_Static_assert в неиспользуемом общем выборе

Похоже, что оператор typeof, вероятно, будет принят в следующем стандарте C, и я искал способ использовать его для создания макроса с использованием переносимого ISO-C, который может получить длину массива, переданного в или не скомпилируется, если в него передан указатель. Обычно общий выбор можно использовать, чтобы вызвать ошибку компилятора при использовании нежелательного типа, исключив его из списка общих ассоциаций, но в этом случае нам нужна ассоциация по умолчанию для работы с массивами любой длины, поэтому вместо этого я пытаюсь вызвать ошибку компилятора для универсальной ассоциации для типа, который нам не нужен. Вот пример того, как может выглядеть макрос:

#define ARRAY_SIZE(X) _Generic(&(X), \
        typeof(&X[0]) *: sizeof(struct{_Static_assert(0, "Trying to get the array length of a pointer"); int _a;}), \
        default: (sizeof(X) / sizeof(X[0])) \
)

Проблема в том, что _Static_assert срабатывает, даже если выбранная общая ассоциация является ассоциацией по умолчанию. Для простоты, поскольку рассматриваемая проблема не связана ни с чем, представленным в C23, мы создадим тестовую программу, которая явно работает, чтобы отклонить указатель на int:

#include <stdio.h>
#include <stdlib.h>

#define ARRAY_SIZE(X) _Generic(&(X), \
        int **: sizeof(struct{_Static_assert(0, "Trying to get the array length of a pointer"); int _a;}), \
        default: (sizeof(X) / sizeof(X[0])) \
)

int main(void) {

    int x[100] = {0};
    int *y = x;
    int (*z)[100] = {&x};

    printf("length of x: %zu\n", ARRAY_SIZE(x));
    printf("length of y: %zu\n", ARRAY_SIZE(y));
    printf("length of z: %zu\n", ARRAY_SIZE(z));
    printf("length of *z: %zu\n", ARRAY_SIZE(*z));
    return EXIT_SUCCESS;

}

Создавая вышеизложенное с помощью -std=c11, я обнаружил, что _Static_assert срабатывает на всех расширениях ARRAY_SIZE, хотя я ожидал, что будут проблемы только с указателями, которые будут использовать общую ассоциацию int **.

Согласно 6.5.1.1 p3 стандарта C11 для общего выбора,

None of the expressions from any other generic association of the generic selection is evaluated

Является ли это ошибкой в ​​gcc и clang, или есть что-то, что я пропустил в стандарте, что может привести к оценке времени компиляции этого _Static_assert в неиспользуемой универсальной ассоциации?

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
6
0
77
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Неважно, какой общий выбор оценивается.

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

Ответ принят как подходящий

Вы не можете смешивать _Static_assert с выражениями, которые должны возвращать значение, такими как макрос, похожий на функцию. Возможно, вы могли бы обойти это с помощью «статического утверждения бедняка», как один из уродливых трюков, которые мы использовали до C11:

#define POOR_STATIC_ASSERT(expr) (int[expr]){0}

#define CHECK(X) _Generic((&X), \
        int **: 0,\
        default: (sizeof(X) / sizeof(X[0])) \
)

#define ARRAY_SIZE(X) ( (void)POOR_STATIC_ASSERT(CHECK(X)), CHECK(X) )

Здесь вызывается оператор запятой, чтобы макрос CHECK возвращал размер или ноль, в зависимости от того, допустим тип или нет. Затем снова вызовите тот же макрос, чтобы вернуть его из функционального макроса ARRAY_SIZE. Это приведет к некоторой загадочной ошибке от компилятора ISO C, такой как «ошибка: ISO C запрещает массив нулевого размера».


Следующая проблема заключается в том, что &(X) в _Generic ни в коем случае не сводится к int**, поэтому этот макрос не является безопасным или надежным. Однако, что касается размеров массивов, мы можем использовать одну хитрость. Указатель на массив без размера (неполный тип) совместим с любым массивом элемента того же типа независимо от его размера. Макрос можно переписать так:

#define POOR_STATIC_ASSERT(expr) (int[expr]){0}

#define CHECK(X) _Generic((&X),              \
        int (*)[]: sizeof(X) / sizeof(X[0]), \
        default: 0)

#define ARRAY_SIZE(X) ( (void)POOR_STATIC_ASSERT(CHECK(X)), CHECK(X) )

Это будет работать для любого массива int независимо от размера, но не для всего остального.

Мне нравится такой подход. Я пытался найти другие способы форсирования ошибок компилятора, но все они заключались в том, чтобы поместить ошибку в один из общих вариантов выбора, а не пытаться использовать значение из выбора для форсирования ошибки. Пара дополнительных вопросов к этому ответу: есть ли причина не использовать отрицательное значение, а не 0? Компилятор, который я тестировал (clang 5), разрешал массивы нулевой длины через расширение даже при компиляции со стандартом iso c11.

Christian Gibbons 25.03.2022 17:24

@ChristianGibbons Думаю, вы могли бы использовать и отрицательные значения. Или просто скомпилируйте с помощью -std=c17 -pedantic-errors, чтобы отключить расширения gcc.

Lundin 25.03.2022 17:25

Второе дополнение: можете ли вы подробнее рассказать о последнем сегменте, что он не обязательно сводится к int **? В этом примере я пытался упростить проблему, чтобы иметь дело только с int, но конечная цель — использовать C23 _Typeof (или любую другую нотацию, которую он использует). Тот факт, что можно использовать массив без размера, делает его немного спорным, но мне все еще любопытен сценарий, в котором случай int ** не работает (или, более того, typeof(&X[0]) *)

Christian Gibbons 25.03.2022 17:33

Я также только что попытался заменить содержимое вашего POOR_STATIC_ASSERT своим (sizeof(struct{ _Static_assert(expr, "Assert message"); int _a;})), и это действительно сработало. Таким образом, кажется, что ключом было переместить утверждение за пределы общего выбора, и теперь может быть создано правильное сообщение об ошибке компилятора.

Christian Gibbons 25.03.2022 17:44

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

Christian Gibbons 25.03.2022 18:55

Используя некоторые предложения из ответа Лундина, я придумал следующее решение упрощенной проблемы:

#define STATIC_ASSERT_EXPRESSION(X, ERROR_MESSAGE) (sizeof(struct {_Static_assert((X), ERROR_MESSAGE); int _a;}))

#define NO_POINTERS(X) _Generic(&(X), \
    int (*)[]: 1, \
    default: 0 \
)

#define ARRAY_SIZE(X) ( (void)STATIC_ASSERT_EXPRESSION(NO_POINTERS(X), "Cannot retrieve the number of array elements from a pointer"), (sizeof(X) / sizeof(X[0])) )

Чтобы фактическим вариантом использования был универсальный тип с использованием typeof, который должен соответствовать стандарту C23, замените макрос NO_POINTERS следующим образом:

#define NO_POINTERS(X) _Generic(&(X), \
    typeof(*X) (*)[]: 1, \
    default: 0 \
)

Перемещая _Static_assert за пределы общего выбора, он будет оцениваться только со значением, которое фактически возвращается из выбора, поэтому он не будет сброшен из-за наличия в неиспользованном выборе. Кроме того, вычисление количества элементов также было удалено из общего выбора, чтобы выражение общего выбора можно было безопасно использовать в статическом утверждении, даже если ваш массив был массивом переменной длины, который требует, чтобы его размер вычислялся во время выполнения. .

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

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

Другие вопросы по теме