Как заставить GCC оценивать функции во время компиляции?

Я думаю о следующей проблеме: я хочу запрограммировать микроконтроллер (скажем, мегатипа AVR) с помощью программы, которая использует какие-то справочные таблицы.

Первой попыткой было бы найти таблицу в отдельном файле и создать ее с помощью любого другого языка сценариев/программы/.... В этом случае необходимо приложить некоторые усилия для создания необходимых исходных файлов для C.

Моя мысль заключалась в том, чтобы использовать препроцессор и компилятор для обработки вещей. Я попытался реализовать это с помощью таблицы значений синуса (просто в качестве примера):

#include <avr/io.h>
#include <math.h>

#define S1(i,n) ((uint8_t) sin(M_PI*(i)/n*255))
#define S4(i,n) S1(i,n), S1(i+1,n), S1(i+2,n), S1(i+3,n)

uint8_t lut[] = {S4(0,4)};

void main()
{
    uint8_t val, i;

    for(i=0; i<4; i++)
    {
        val = lut[i];
    }
}

Если я скомпилирую этот код, я получу предупреждение о функции sin. Дальше в сборке ничего нет в разделе .data. Если я просто уберу sin в третьей строке, я получу данные в сборке. Понятно, что вся информация доступна во время компиляции.

Можете ли вы сказать мне, есть ли способ добиться того, чего я хочу: компилятор вычисляет как можно больше значений в автономном режиме? Или лучше всего использовать внешний скрипт/программу/... для вычисления записей таблицы и добавления их в отдельный файл, который будет просто #included?


person Christian Wolf    schedule 22.01.2015    source источник
comment
довольно много усилий - с хорошим языком сценариев? конечно меньше, чем атаковать проблему с помощью C....   -  person Karoly Horvath    schedule 22.01.2015
comment
C++11 (улучшенный с помощью C++14) имеет constexpr как подсказку компилятору для выполнения функции во время компиляции.   -  person johannes    schedule 22.01.2015
comment
@johannes: constexpr не преимущество. Он просто позволяет вычислять выражение во время компиляции (и даже принуждение компилятора к нему, например, путем присвоения перечислению, не препятствует его повторной оценке во время выполнения в другом месте в тот же исходный файл!). Он ничего не навязывает и не намекает. Тем не менее, мой GCC прекрасно оптимизирует фрагмент кода в OP в таблицу поиска, оцениваемую во время компиляции (без каких-либо особых танцев).   -  person Damon    schedule 22.01.2015
comment
Как я прокомментировал ниже предупреждение о sin станет ошибкой, если вы используете -fno-builtin, потому что gcc в настоящее время обрабатывает большинство математических функций как постоянные выражения, если он использует встроенную версию.   -  person Shafik Yaghmour    schedule 22.01.2015
comment
Ваш sin вернет только -1/255, 0 или 1. Возможно, вы будете использовать `((uint8_t) (sin(M_PI*(i)/n)+1)/2*255)`   -  person 12431234123412341234123    schedule 04.07.2017


Ответы (3)


Общая проблема здесь заключается в том, что вызов sin делает эту инициализацию де-факто незаконной, в соответствии с правилами языка C, поскольку это не константное выражение само по себе, и вы инициализируете массив статической длительности хранения< /em>, что требует этого. Это также объясняет, почему ваш массив не находится в разделе .data.

C11 (N1570) §6.6/2,3 Постоянные выражения (выделено мной)

Константное выражение может быть вычислено во время трансляции, а не во время выполнения, и, соответственно, может использоваться в любом месте, где может быть константа.

Константные выражения не должны содержать присваивание, приращение, уменьшение, вызов функции или операторы запятой, за исключением случаев, когда они содержатся в подвыражении, которое не оценивается.115)

Однако, как следует из комментария @ShafikYaghmour, GCC заменит вызов функции sin своим встроенным аналогом (если не указан параметр -fno-builtin), который, вероятно, будет рассматриваться как постоянное выражение. Согласно 6.57 Другие встроенные функции, предоставляемые GCC:

GCC включает встроенные версии многих функций из стандартной библиотеки C. Версии с префиксом __builtin_ всегда рассматриваются как имеющие то же значение, что и функция библиотеки C, даже если вы укажете параметр -fno-builtin.

person Grzegorz Szpetkowski    schedule 22.01.2015
comment
Это, вероятно, работает, потому что в настоящее время gcc обрабатывает встроенные математические функции как константные выражения, использование-fno-builtin приводит к сбою аналогичного кода с использованием gcc, но это только в противном случае генерирует предупреждение. - person Shafik Yaghmour; 22.01.2015
comment
@ShafikYaghmour: Почему тогда нет данных в разборке. Я совершенно не уверен, что произойдет, если я попытаюсь получить доступ к данным... - person Christian Wolf; 22.01.2015
comment
@ChristianWolf: Вы можете форсировать это с помощью __builtin_sin, но я согласен с Шафиком, что, скорее всего, это должно сработать. Может быть, вы используете какой-то порт avr-gcc, который его не поддерживает? - person Grzegorz Szpetkowski; 22.01.2015
comment
@GrzegorzSzpetkowski: Если я попробую это (заменив sin на __builtin_sin), данные будут зарегистрированы в разделе .bss и обнулены при запуске - я проверил, увеличив массив и посмотрев размер массива. - person Christian Wolf; 22.01.2015
comment
Ах, я понял. Я забыл пару paramtesis. Таким образом, я всегда оценивал число, кратное пи (которое на самом деле равно 0 и оптимизировано). По исправлению, что работает корректно. - person Christian Wolf; 23.01.2015

То, что вы пытаетесь сделать, не является частью языка C. В таких ситуациях я написал код по следующему шаблону:

#if GENERATE_SOURCECODE
int main (void)
{
    ... Code that uses printf to write C code to stdout
}
#else
    // Source code generated by the code above
    ... Here I paste in what the code above generated

    // The rest of the program
#endif

Каждый раз, когда вам нужно его изменить, вы запускаете код с определенным GENERATE_SOURCECODE и вставляете вывод. Хорошо работает, если ваш код самодостаточен, а сгенерированный вывод всегда изменяется только в том случае, если изменяется код, его генерирующий.

person gnasher729    schedule 22.01.2015
comment
Это не оптимально в том смысле, что я занимаюсь кросс-компиляцией. Таким образом, это может быть или не быть в порядке. Я предполагаю, что усилия намного выше, чем поддержка двух разных файлов, поскольку все файлы заголовков должны быть обменены и, следовательно, внутри блоков #if. Выглядит противно мне, извините. - person Christian Wolf; 22.01.2015

Прежде всего, само собой разумеется, что вы должны оценить (вероятно, экспериментальным путем), стоит ли это делать. Ваша таблица поиска увеличит размер ваших данных и усилия программиста, но может обеспечить или не обеспечить необходимое вам увеличение скорости выполнения.

Если вы все еще хотите сделать это, я не думаю, что препроцессор C может сделать это напрямую, потому что у него нет средств для итерации или рекурсии.

Самый надежный способ сделать это — написать программу на C или другом языке для вывода исходного кода C для таблицы, а затем включить этот файл в вашу программу с помощью препроцессора. Если вы используете такой инструмент, как make, вы можете создать правило для создания файла таблицы, и ваш файл .c будет зависеть от этого файла.

С другой стороны, если вы уверены, что никогда не будете изменять эту таблицу, вы можете написать программу, которая сгенерирует ее один раз и просто вставит.

person Samuel Edwin Ward    schedule 22.01.2015