Чтобы показать тему, я собираюсь использовать C, но тот же макрос можно использовать также в С++ (с или без struct
), поднимая тот же вопрос.
Я придумал этот макрос
#define STR_MEMBER(S,X) (((struct S*)NULL)->X, #X)
Его цель состоит в том, чтобы иметь строки (const char*
) существующего члена struct
, так что, если член не существует, компиляция не выполняется. Минимальный пример использования:
#include <stdio.h>
struct a
{
int value;
};
int main(void)
{
printf("a.%s member really exists\n", STR_MEMBER(a, value));
return 0;
}
Если value
не был членом struct a
, код не будет компилироваться, и это то, что я хотел.
Оператор запятой должен оценивать левый операнд и затем отбрасывать результат выражения (если он есть), так что я понимаю, что обычно этот оператор используется, когда оценка левого операнда имеет побочные эффекты.
В этом случае, однако, нет (предполагаемых) побочных эффектов, но, конечно, это работает, если компилятор фактически не производит код, который оценивает выражение, поскольку в противном случае он имел бы доступ к struct
, расположенному при NULL
и произойдет ошибка сегментации.
Gcc/g++ 6.3 и 4.9.2 никогда не приводили этот опасный код даже при -O0
, как если бы они всегда могли "видеть", что оценка не имеет побочных эффектов и поэтому ее можно пропустить.
Добавление volatile
в макрос (например, поскольку доступ к этому адресу памяти является желательным побочным эффектом) был единственным способом вызвать ошибку сегментации.
Итак, вопрос: есть что-либо в стандартах на языках C и С++, что гарантирует, что компиляторы всегда будут избегать фактической оценки левого операнда оператора запятой, когда компилятор может быть уверен, что оценка не имеет стороны эффекты?
Примечания и фиксация
Я не прошу судить о макросе, как есть, и о возможности использовать его или сделать его лучше. Для целей этого вопроса макрос плохой тогда и только тогда, когда он вызывает поведение undefined - то есть, если и только если это опасно, потому что компиляторам разрешено генерировать "оценочный код", даже если у этого нет побочных эффектов.
У меня уже есть два очевидных решения: "reifying" struct
и использование offsetof
. Первый требует доступной области памяти, такой большой, как самый большой struct
, который мы используем в качестве первого аргумента STR_MEMBER
(например, возможно, статический союз мог бы...). Последний должен работать безупречно: он дает смещение, которого мы не интересуем и избегаем проблемы с доступом - действительно, я предполагаю gcc, потому что это компилятор, который я использую (отсюда и тег), и что его встроенный offsetof
ведет себя.
При исправлении offsetof
макрос становится
#define STR_MEMBER(S,X) (offsetof(struct S,X), #X)
Запись volatile struct S
вместо struct S
не вызывает segfault.
Также можно приветствовать предложения о других возможных "исправлениях".
Добавлено примечание
Собственно, реальный случай использования был в С++ в статическом хранилище struct
. Кажется, что это хорошо на С++, но как только я попробовал C с кодом, близким к оригиналу, а не с кипяченой для этого вопроса, я понял, что C совсем не доволен этим:
error: initializer element is not constant
C хочет, чтобы структура была инициализирована во время компиляции, вместо этого С++ это хорошо с этим.