C стандартная несогласованность упрощения адресации

Раздел §6.5.3.2 "Операторы адреса и косвенности" ¶3 говорит (только для соответствующего раздела):

Унарный и оператор возвращает адрес своего операнда....  Если операнд является результатом унарного оператора *, ни этот оператор, ни оператор & не оцениваются, и результат как бы опускался, за исключением того, что ограничения на операторы все еще применяются, и результат не является именующий. Аналогично, если операнд является результатом оператора [], ни оператор &, ни унарный *, который подразумевается [], не оцениваются, а результат выглядит так, как если бы оператор & был удален и оператор [] были заменены на a +....

Это означает, что это:

#define NUM 10
int tmp[NUM];
int *i = tmp;
printf("%ti\n", (ptrdiff_t) (&*i - i) );
printf("%ti\n", (ptrdiff_t) (&i[NUM] - i) );

Должно быть совершенно законным, печатать 0 и NUM (10). Стандарт кажется очень ясным, что оба этих случая должны быть оптимизированы.

Однако, похоже, для оптимизации не требуется следующее:

struct { int a; short b; } tmp, *s = tmp;
printf("%ti\n", (ptrdiff_t) (&s->b - s) );

Это кажется ужасно непоследовательным. Я не вижу причин, чтобы приведенный выше код не печатал дополнение sizeof(int) plus (маловероятное) (возможно, 4).

Упрощение выражения &-> будет таким же концептуальным (IMHO) как &[], простым адресом-плюс-смещением. Это даже смещение, которое будет определяться во время компиляции, а не потенциально runtime с оператором [].

Есть ли что-нибудь в обосновании того, почему это так кажется непоследовательным?

Ответ 1

В вашем примере &i[10] на самом деле не является законным: он становится i + 10, который становится NULL + 10, и вы не можете выполнить арифметику по нулевому указателю. (6.5.6/8 перечислены условия, при которых может выполняться арифметика указателя)

Во всяком случае, это правило было добавлено в C99; его нет в C89. Я понимаю, что он был добавлен в значительной степени, чтобы сделать код следующим образом:

int* begin, * end;
int v[10];

begin = &v[0];
end = &v[10];

Эта последняя строка технически недействительна в C89 (и на С++), но разрешена в C99 из-за этого правила. Это было относительно незначительное изменение, которое сделало широко используемую конструкцию четко определенной.

Поскольку вы не можете выполнить арифметику на нулевом указателе, ваш пример (&s->b) в любом случае будет недействительным.

Что касается того, почему существует эта "непоследовательность", я могу только догадываться. Вероятно, никто не думал, чтобы это было согласованным, или никто не видел убедительного примера для этого. Возможно, это было рассмотрено и в конечном итоге отвергнуто. Нет никаких замечаний о сокращении &* в Обоснование. Вы можете найти определенную информацию в документах WG14, но, к сожалению, они, похоже, довольно плохо организованы, поэтому траление через них может быть утомительным.

Ответ 2

Я думаю, что правило не было добавлено для целей оптимизации (что это значит, что правило as-if не работает?), но чтобы разрешить &t[sizeof(t)/sizeof(*t)] и &*(t+sizeof(t)/sizeof(*t)) поведение undefined без он (писать такие вещи напрямую может показаться глупым, но добавить слой или два макроса, и это может иметь смысл). Я не вижу случая, когда специальная обсадка & p- > m принесет такую ​​выгоду. Обратите внимание, что, как заметил Джеймс, &p[10] с p нулевой указатель все еще undefined; &p->m с p нулевой указатель аналогичным образом остался бы недействительным (и я должен признать, что я не вижу никакого использования, когда p - нулевой указатель).

Ответ 3

Я считаю, что компилятор может выбрать пакет по-разному, возможно добавив дополнение между членами структуры, чтобы увеличить скорость доступа к памяти. Это означает, что вы не можете точно сказать, что b будет всегда быть смещенным в 4 раза. Единственное значение не имеет той же проблемы.

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


изменить:

У меня есть другая теория...

много раз компилятор будет оптимизировать абстрактное синтаксическое дерево сразу после лексического анализа и синтаксического анализа. Это означает, что он найдет такие вещи, как операторы, которые сокращают и выражения, которые оценивают константу, и уменьшают эти разделы дерева до одного node. Это также означает, что информация о структурах недоступна. более поздние оптимизации, которые происходят после того, как некоторые генерации кода могут учесть это, поскольку они содержат дополнительную информацию, но для таких вещей, как обрезка AST, эта информация еще не существует.