Несоответствие упрощения адресации стандарта C - PullRequest
11 голосов
/ 05 февраля 2011

Раздел §6.5.3.2 «Операторы адреса и косвенности» says3 гласит (только соответствующий раздел):

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

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

#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).

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

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

Ответы [ 3 ]

4 голосов
/ 05 февраля 2011

В вашем примере &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 (и в C ++), но разрешена в C99 из-за этогоправить.Это было относительно незначительное изменение, которое сделало широко используемую конструкцию четко определенной.

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

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

2 голосов
/ 05 февраля 2011

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

1 голос
/ 05 февраля 2011

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

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


редактирование:

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

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

...