Подтвердить что ты не робот

Достаточно ли доступ к размеру структуры через адрес члена, с достаточным пространством?

В частности, это следующий код, строка под маркером, ОК?

struct S{
    int a;
};

#include <stdlib.h>

int main(){
    struct S *p;
    p = malloc(sizeof(struct S) + 1000);
    // This line:
    *(&(p->a) + 1) = 0;
}

Люди аргументировали здесь, но никто не дал убедительного объяснения или ссылки.

Их аргументы находятся на немного другой основе, но по существу те же самые

typedef struct _pack{
    int64_t c;
} pack;

int main(){
    pack *p;
    char str[9] = "aaaaaaaa"; // Input
    size_t len = offsetof(pack, c) + (strlen(str) + 1);
    p = malloc(len);
    // This line, with similar intention:
    strcpy((char*)&(p->c), str);
//                ^^^^^^^
4b9b3361

Ответ 1

Цель, по крайней мере, с тех пор, как стандартизация C в 1989 году заключалась в том, что реализациям разрешено проверять границы массива для доступа к массиву.

Элемент p->a является объектом типа int. C11 6.5.6p7 говорит, что

7 Для целей [аддитивных операторов] указатель на объект, который не является элементом массива, ведет себя так же, как указатель на первый элемент массива длиной один с типом объекта как его тип элемента.

Таким образом,

&(p->a)

является указателем на int; но он также выглядит как указатель на первый элемент массива длины 1, а int - тип объекта.

Теперь 6.5.6p8 позволяет вычислить &(p->a) + 1, который является указателем только на конец массива, поэтому нет undefined. Однако разыменование такого указателя недействительно. Из Приложение J.2, где указано, поведение undefined, когда:

Добавление или вычитание указателя на объект массива или целочисленный тип или только за его пределами приводит к результату, который указывает непосредственно за объект массива и используется как операнд унарного оператора *, который оценивается (6.5 0,6).

В приведенном выше выражении существует только один массив, один (как будто) с ровно 1 элементом. Если &(p->a) + 1 разыменовывается, массив с длиной 1 получает доступ за пределы и undefined поведение происходит, то есть

поведение [...], для которого [The C11] Standard не предъявляет требований

С отметьте, что:

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

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


Утверждалось, что стандартный текст С11 написан смутно, и намерение комитета должно состоять в том, чтобы это действительно было разрешено, и раньше было бы все в порядке. Это не верно. Прочтите часть ответа комитета на [Отчет о дефектах № 017 от 10 декабря 1992 года до C89].

Вопрос 16

[...]

ответ

Для массива массивов разрешенная арифметика указателя в подпункт 6.3.6, стр. 47, строки 12-40 следует понимать интерпретация использования объекта слова как обозначение объект, определяемый непосредственно типом и значением указателя, а не другим объекты, связанные с этим, соприкосновением. Поэтому, если выражение превышает эти разрешения, поведение undefined. Например, следующий код имеет поведение undefined:

 int a[4][5];

 a[1][7] = 0; /* undefined */ 

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

(выделено жирным шрифтом)

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

Если вы хотите обратиться к последовательным struct s, вы всегда можете перенести указатель на первый член и направить его как указатель на struct, а затем:

*(int *)((S *)&(p->a) + 1) = 0;

Ответ 2

Это поведение undefined, поскольку вы обращаетесь к тому, что не является массивом (int a внутри struct S) в качестве массива, и за пределами этого.

Правильный способ добиться того, что вы хотите, - использовать массив без размера в качестве последнего члена struct:

#include <stdlib.h>

typedef struct S {
    int foo;    //avoid flexible array being the only member
    int a[];
} S;

int main(){
    S *p = malloc(sizeof(*p) + 2*sizeof(int));
    p->a[0] = 0;
    p->a[1] = 42;    //Perfectly legal.
}

Ответ 3

Стандарт

C гарантирует, что §6.7.2.1/15:

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

&(p->a) эквивалентен (int *)p. &(p->a) + 1 будет адресом элемента второй структуры. В этом случае есть только один элемент, в структуре не будет никакого дополнения, так что это будет работать, но там, где будет заполнение, этот код сломается и приведет к поведению undefined.