Является разыменованием нулевого указателя, действительным в размере операции

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

typedef struct {
  double length;
  unsigned char nPlaced;
  unsigned char path[0];
}


RouteDefinition* Alloc_RouteDefinition()
{
  // NB: The +nBags*sizeof.. trick "expands" the path[0] array in RouteDefinition
  // to the path[nBags] array
  RouteDefinition *def = NULL;
  return (RouteDefinition*) malloc(sizeof(RouteDefinition) + nBags * sizeof(def->path[0]));
}

Почему это работает? Я понимаю, что размер char * будет определять размер указателя на заданную архитектуру, но не должен ли он разбиваться и записываться при разыменовании NULL -pointer?

Ответ 1

Почему это работает?

Это работает, потому что sizeof является конструкцией времени компиляции, за исключением массивов переменной длины вообще не оценивается. Если мы посмотрим на C99 draft standard 6.5.3.4 Оператор sizeof оператора в пункте 2 говорит (внимание мое):

[...] Размер определяется по типу операнда. Результат - целое число. Если тип операнда является типом переменной длины, то операнд оценивается; в противном случае операнд не оценивается, а результат представляет собой целочисленную константу.

мы также видим следующий пример в параграфе 5, который подтверждает это:

double *dp = alloc(sizeof *dp);
       ^^^                ^
                          |                                 
                          This is not the use of uninitialized pointer 

Во время компиляции тип выражения с определением определяется для вычисления результата. Мы можем еще раз продемонстрировать это в следующем примере:

int x = 0 ;
printf("%zu\n", sizeof( x++ ));

который не будет увеличивать x, что довольно аккуратно.

Обновить

Как я отмечаю в моем ответе на Почему sizeof (x ++) не увеличивает x? есть исключение из sizeof является операцией времени компиляции, а именно, когда операндом является массив переменной длины (VLA). Хотя я ранее не указывал на это, цитата из 6.5.3.4 выше говорит об этом.

Хотя в C11 в отличие от C99 не определено, оценивается ли sizeof или нет в этом случае.

Также обратите внимание, что существует версия С++ этого quesiton: Не оценивает ли выражение, к которому применяется sizeof, делает его законным для разыменования нулевого или недопустимого указателя внутри sizeof в С++?.

Ответ 2

Оператор sizeof - это чистая операция компиляции. Ничего не сделано во время выполнения, поэтому он отлично работает.

Кстати, элемент path на самом деле не является указателем, поэтому технически он не может быть NULL.

Ответ 3

Утверждение, что sizeof является чисто компиляционной конструкцией (как в настоящее время существующие ответы), не совсем точно. Поскольку C99, sizeof не является чисто компиляцией времени. Операнд sizeof оценивается во время выполнения типа операнда - это VLA. Ответы, опубликованные до сих пор, похоже, игнорируют эту возможность.

Ваш код в порядке, поскольку он не связан с VLA. Однако, что-то вроде этого может быть другой историей.

unsigned n = 10;
int (*a)[n] = NULL; // `a` is a pointer to a VLA 

unsigned i = 0;
sizeof a[i++];      // applying `sizeof` to a VLA

В соответствии со стандартом C99 аргумент sizeof должен быть оценен (т.е. предполагается, что i увеличивается, см. https://ideone.com/9Fv6xC). Тем не менее, я не совсем уверен, что разыменование нулевой точки в a[0] предполагается производить здесь поведение undefined.