У меня есть следующие макросы определения:

#define NHID 5
#define NENT 10
#define NOUT 4
#define NWEIS (NENT + 1) * NHID + (NHID + 1) * NOUT

Таким образом, каждый раз, когда компилятор находит «NWEIS», он заменяет «NEWIS» на «(NENT + 1) * NHID + (NHID + 1) * NOUT». Но я не этого хочу. Я хочу, чтобы он заменил "NWEIS" фактическим значением = 79, без необходимости объявлять дополнительные переменные в памяти. Есть ли достойный способ сделать это?

1
Veiga 30 Окт 2015 в 05:12

3 ответа

Лучший ответ

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

Давайте посмотрим, что сделает разумный компилятор.

Предположим, у вас есть этот код.

#define NHID 5
#define NENT 10
#define NOUT 4
#define NWEIS (NENT + 1) * NHID + (NHID + 1) * NOUT

int f()
{
    return NWEIS;
}

Разумный компилятор, очевидно, расширит его до:

int f()
{
    return (NENT + 1) * NHID + (NHID + 1) * NOUT;
}

Следующим шагом будет:

int f()
{
    return (10 + 1) * 5 + (5 + 1) * 4;
}

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

int f()
{
    return 79;
}

Обратите внимание, что эта функция настолько мала, что разумный компилятор изо всех сил постарается встроить функцию.


Однако гораздо предпочтительнее сделать это:

constexpr int NHID = 5;
constexpr int NENT = 10;
constexpr int NOUT = 4;
constexpr int NWEIS = (NENT + 1) * NHID + (NHID + 1) * NOUT;
2
30 Окт 2015 в 02:51

Просто используйте

const int NHID = 5;
const int NENT 10;
const int NOUT 4;
const int NWEIS = (NENT + 1) * NHID + (NHID + 1) * NOUT;

Хороший оптимизатор заменит эти значения во время компиляции и не будет помещать какие-либо переменные в память, если вы не сделаете что-то вроде их адреса. Тогда у вас есть безопасность типов и область видимости C ++ без зла макросов.

(Имена в верхнем регистре по соглашению зарезервированы для макросов, поэтому вы можете немного переименовать их)

1
Neil Kirk 30 Окт 2015 в 02:48

1

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

Например, рассмотрим программу:

#define NHID 5
#define NENT 10
#define NOUT 4
#define NWEIS (NENT + 1) * NHID + (NHID + 1) * NOUT
int main (void) { return NWEIS; }

Вот результат препроцессора из gcc -E:

int main (void) { return (10 + 1) * 5 + (5 + 1) * 4; }

И вот соответствующая строка кода ассемблера, которую он генерирует с помощью gcc -S (возвращаемое значение помещается в регистр eax):

movl   $79, %eax

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

Конечно, я все еще пытаюсь найти макросы для быстрого и грязного кода, но в основном потому, что я старый тупица, созданный в первые дни C, еще до того, как у нас появились прототипы :-)

Возможно, стоит переосмыслить и их использование, так как вы можете заменить это чем-то вроде:

const int nhid  =  5;
const int nent  = 10;
const int nout  =  4;
const int nweis = (nent + 1) * nhid + (nhid + 1) * nout;

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


1 Полную информацию можно найти в стандарте C ++ 11, раздел 16.3 Macro replacement.

Достаточно сказать, что существуют определенные варианты использования # и ## в макросе, которые предотвращают дальнейшую замену этого токена (первый заменяет токен строковым литералом символов, а второй объединяет несколько токенов в разные токены).

Поскольку вы их не используете, здесь это не имеет значения.

3
paxdiablo 30 Окт 2015 в 02:57