2014-11-03 3 views
7

Стандарт C99 гласит:Разница указателей между членами структуры?

Когда два указателя вычитаются, то оба указывают на элементы одного и того же объекта массива, или за последним элементом массива объектов

Рассмотрим следующее код:

struct test { 
    int x[5]; 
    char something; 
    short y[5]; 
}; 

... 

struct test s = { ... }; 
char *p = (char *) s.x; 
char *q = (char *) s.y; 
printf("%td\n", q - p); 

Это явно нарушает вышеуказанное правило, поскольку p и q указатели указывают на разные «объекты массива», и, согласно правилу, разница q - p не определена.

Но на практике почему такое поведение всегда приводит к неопределенному поведению? В конце концов, элементы структуры выложены последовательно (так же, как элементы массива), с любым возможным дополнением между членами. Правда, количество отступов будет различаться в разных реализациях, что повлияет на результат вычислений, но почему этот результат должен быть «неопределенным»?

Вопрос в том, можем ли мы предположить, что стандарт просто «неосведомлен» по этой проблеме или есть веская причина не расширять это правило? Не удалось ли перевести вышеуказанное правило в «, оба должны указывать на элементы одного и того же объекта массива или на элементы той же структуры»?

Мое единственное подозрение - это сегментированные архитектуры памяти, в которых члены могут оказаться в разных сегментах. Это так?

Я также подозреваю, что именно по этой причине GCC определяет свой собственный __builtin_offsetof, чтобы иметь «соответствие стандартам» определение макроса offsetof.

EDIT:

Как уже отмечалось, арифметика недействительных указателей не допускается стандартом. Это расширение GNU, которое выдает предупреждение только тогда, когда GCC передается -std=c99 -pedantic. Я заменяю указатели void * указателями char *.

+6

и в вашем примере арифметика указателя на void запрещена в любом случае. если типы arent одинаковы, как вы можете их вычесть? – camelccc

+0

Вы правы, что арифметика на указателях пустот не допускается стандартом, и это расширение GNU. Представьте, что оба указателя являются «char *». –

+0

gcc разрешает арифметику указателя на 'void *' путем обработки 'sizeof (void)' как 1 (так же, как 'char *'). Так что для целей вашего вопроса это не имеет значения. –

ответ

3

Вычисления и реляционные операторы (по типу char*) между адресами члена одной и той же структуры четко определены.

Любой объект можно рассматривать как массив из unsigned char.

Котировка N1570 6.2.6.пункт 1 4:

Значение, хранящееся в небитовом-полевых объектах любого другого типа объекта состоит из п × CHAR_BIT бит, где п размера объекта этого типа, в байт. Значение может быть скопирована в объект типа unsigned char [н] (например, с помощью memcpy); результирующий набор байтов составляет , который называется представлением объекта значения.

...

Мое единственное подозрение на сегменты архитектуры памяти, где члены может в конечном итоге в различных сегментах. Это так?

№ п. Для системы с сегментированной архитектурой памяти, как правило, компилятор будет устанавливать ограничение, согласно которому каждый объект должен вписываться в один сегмент. Или он может разрешать объекты, которые занимают несколько сегментов, но он все равно должен обеспечить правильную работу арифметики указателей и сравнений.

+0

+1 Я думаю, что умудряется найти точку лучше, чем мой ответ. – user694733

+2

Я не уверен. Если указатель на объект всегда можно рассматривать как указатель на внешний охватывающий объект, например, структурный хак также будет легальным ... – mafso

+0

@mafso Ну, в стандарте есть * запись * в индексе: 'struct hack, см. элемент гибкого массива' ... – user694733

1

Да, вам разрешено выполнять указатель arithmetric по структуре байтов:

N1570 - 6.3.2.3 Указатели p7:

... Когда указатель на объект преобразуется в указатель тип символа, результат указывает на самый младший адресный байт объекта. Последовательные приращения результата , с точностью до размера объекта, указывают указатели на оставшиеся байты объекта.

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

Не с void* указателями, хотя это нестандартное расширение компилятора. Как упоминалось в параграфе из стандарта, оно применяется только к указателям типа символов.

Edit:

Как mafso отметил в комментариях, это верно только до тех пор, как тип вычитания результата ptrdiff_t, имеет достаточный диапазон для результата. Поскольку диапазон size_t может быть больше ptrdiff_t, и если структура достаточно велика, возможно, адреса слишком далеки друг от друга.

Из-за этого предпочтительно использовать макрос offsetof на элементах структуры и рассчитать результат.

+0

+1 Я также считаю, что слово «элементы» в приведенном мной правиле используется для различения простых указателей «char *» и правильно выровненных указателей типа, соответствующего элементам массива. –

+2

Этот ответ, похоже, подразумевает, что прокладка может считаться объектом, который, как я очень сомневаюсь, был предназначен. –

+0

Этот ответ промахивается. 1. Если 'p + n' сравнивается с' q', это не означает, что определен 'q - p'. 2. Важно то, что здесь объект (вся структура или только член). Я склонен интерпретировать стандарт таким образом, чтобы сказать последнее. – mafso

0

следует отметить следующее:

от стандарта C99, раздел 6.7.2.1:

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

Это не так, что результат вычитания указателя между членами не определен так сильно, насколько он ненадежен (т. Е. Не может быть одинаковым между разными экземплярами одного и того же типа структуры, когда применяется одна и та же арифметика) ,

2

Арифметика указателя требует, чтобы два указателя добавлялись или вычитались как часть одного и того же объекта, потому что это не имеет смысла. В цитируемой части стандарта конкретно упоминаются два несвязанных объекта, таких как int a[b]; и int b[5]. Арифметика указателя требует знать тип объекта, на который указывают указатели (я уверен, что вы уже знаете об этом).

т.е.

int a[5]; 
int *p = &a[1]+1; 

Здесь p рассчитывается путем зная, что &a[1] относится к int объекта и, следовательно, увеличивается на 4 байта (при условии, sizeof(int) равно 4).

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

Давайте рассмотрим пример,

struct test { 
    int x[5]; 
    char something; 
    short y[5]; 
}; 

Указатель arithmatic не допускается с void указателями по стандарту C (Compiling с gcc -Wall -pedantic test.c поймают, что). Я думаю, вы используете gcc, который предполагает, что void* похож на char* и позволяет. Так,

printf("%zu\n", q - p); 

эквивалентно

printf("%zu", (char*)q - (char*)p); 

, как арифметика указателей корректно определено, если указатели указывают на пределах того же самого объекта и символов указателей (char* или unsigned char*).

Использование правильных типов, было бы:

struct test s = { ... }; 
int *p = s.x; 
short *q = s.y; 
printf("%td\n", q - p); 

Теперь, как может q-p быть выполнена? на основе sizeof(int) или sizeof(short)? Как можно рассчитать размер char something;, который находится в середине этих двух массивов?

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

Даже если все члены одного типа (таким образом, не имеют типа проблемы, как указано выше), тогда лучше использовать стандартный макрос offsetof (от <stddef.h>), чтобы получить разницу между элементами структуры, которые имеют аналогичный эффект, как арифметика указателя между участниками:

printf("%zu\n", offsetof(struct test, y) - offsetof(struct test, x)); 

Таким образом, я не вижу необходимости определять арифметику указателя между элементами структуры по стандарту C.

+2

Хороший ответ, но вы забываете, что это разрешено, когда указатели имеют тип 'char *' и указывают на один и тот же объект. Без этого невозможно определить 'offsetof'. –

+0

Конечно. Но я не уверен, где это противоречить или подразумеваю? –

+1

Вы не прямо противоречите этому, но это важная «лазейка», чтобы упомянуть, ИМХО. –

1

Я считаю, что ответ на этот вопрос проще, чем кажется, ОП спрашивает:

, но почему то результат должен быть «не определен»?

Ну, давай посмотрим, что определение неопределенного поведения в проекте C99 стандартной секции 3.4.3:

поведения, при использовании Непереносимой или неточной программе конструкции или ошибочных данных, для которых Настоящий стандарт не устанавливает требований

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

Скорее всего, это было сделано неопределенным поведение, чтобы ограничить число способов может быть создал недопустимый указатель, это согласуется с тем фактом, что мы снабженный offsetof, чтобы удалить одну потенциальной потребность указателя вычитания несвязанных объектов ,

Хотя стандарт не реально определить термин неверный указатель, мы получаем хорошее описание в Rationale for International Standard—Programming Languages—C, которые в разделе 6.3.2.3указатели говорит (курсив мой):

Неявные в стандарте является понятие недействительных указателей. В обсуждении указателей Стандарт обычно ссылается на «указатель на объект » или «указатель на функцию» или «нулевой указатель». Специальный случай в адресной арифметике позволяет указывать указатель только на конец массива. Любой другой указатель недействителен.

C99 обоснование далее добавляет:

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

Это наводит на мысль, нам, что указатель на заполнения бы недопустимый указатель, хотя это трудно доказать, что обивка не является объект, определение объекта говорит:

область хранения данных в среде исполнения, содержимое , которое может представлять значения

и примечания:

Когда ссылка, объект может быть интерпретировано как имеющее конкретный тип ; см. 6.3.2.1.

Я не вижу, как мы можем рассуждать о типа или значение набивки между элементами структуры и, следовательно, они не являются объектами или по крайней мере сильно указывает обивка является не считается объектом .

+0

Я не вижу, как указатель на заполнение может быть недопустимым указателем. Заполнение - это не объект, а часть объекта. В конце концов, стандартные гарантии того, что заполнение будет существовать, только его значение не определено (6.2.6.1p1). См. Keith Thompsons [ответ] (http://stackoverflow.com/a/26718633/694733). – user694733

Смежные вопросы