Эффективные правила типа применительно к строгому алиасингу - PullRequest
2 голосов
/ 19 апреля 2020

Итак, я бился головой о Строгом Правиле Псевдонима и действующих правилах типа за последние пару дней. Хотя дух этого достаточно ясен, я бы хотел получить хорошее техническое понимание правил. Пожалуйста, обратите внимание, что я рассмотрел много связанных вопросов по SO, но я не чувствую, что на вопросы, которые будут представлены здесь, были даны ответы таким образом, который действительно находится со мной в любом другом месте.

Этот вопрос разделен на две части.

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

Вторая часть вопроса касается моего понимания SAR.

Часть 1: Правила действующего типа

Предложение 1

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

Это довольно ясно - объявленный объект, такой как int x, имеет постоянный эффективный тип, то есть тот тип, с которым он объявлен (в данном случае int).

Предложение 2

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

«Объект, не имеющий объявленного типа», как правило, является динамически размещаемым объектом.

Когда мы храним данные внутри выделенного объекта (, независимо от того, имеет ли он уже эффективный тип ), эффективным типом объекта становится тип lvalue, используемый для доступа к данным для хранения (если только lvalue не имеет символа тип). Например:

int* x = malloc(sizeof(int)); // *x has no effective type yet
*x = 10; // *x has effective type int, because the type of lvalue *x is int

Также можно изменить эффективный тип объекта, который уже имеет эффективный тип. Например:

float* f = (float*) x;
*f = 20.5; // *x now has effective type float, because the type of lvalue *f is float.

Предложение 3

Если значение копируется в объект, не имеющий объявленного типа, используя memcpy или memmove, или копируется как массив символьного типа, тогда действующий тип измененного объекта для этого доступа и для последующих обращений, которые не изменяют значение, является эффективным типом объекта, из которого копируется значение, если оно имеет его.

Это означает, что когда мы устанавливаем значение в выделенный объект, если значение устанавливается через lvalue типа, совместимого с char* (или через memcpy и memmove), эффективный тип Объект становится эффективным типом данных, которые копируются в него. Например:

int* int_array = malloc(sizeof(int) * 5); // *int_array has no effective type yet
int other_int_array[] = {10, 20, 30, 40, 50};
char* other_as_char_array = (char*) other_int_array;
for (int i = 0; i < sizeof(int) * 5; i++) {
    *((char*) int_array + i) = other_as_char_array[i];
}
// *int_array now has effective type int

Предложение 4

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

У меня есть два вопроса относительно этой части:

A. Под " для всех других доступов " означает ли текст просто "для всех чтение доступов"?

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

B. Определенный объект в памяти имеет только один эффективный тип. Итак, что означает текст «Для всех других доступов » ... Это не вопрос доступа, это вопрос объективного эффективного типа объекта. Не так ли? Пожалуйста, уточните язык текста.

Часть 2: Вопрос о строгом алиасинге

Описание правила строгого алиасинга начинается примерно так (выделено мной):

Объект должен иметь свое хранимое значение, доступ к которому возможен только через выражение lvalue, которое имеет один из следующих типов [...]

Когда текст говорит «сохраненное значение» доступ "- означает ли это доступ как для чтения, так и для записи, или только для чтения?

В качестве другого способа задать этот вопрос: представляет ли следующий код нарушение строгого псевдонима или это законно?

int* x = malloc(sizeof(int)); // *x - no effective type yet
*x = 8; // *x - effective type int
printf("%d \n", *x); // access the int object through lvalue *x

float* f = (float*) x; // casting itself is legal
*f = 12.5; // effective type of *x changes to float - *** is this a SAR violation? ***
printf("%g \n", *f); // access the float object through lvalue *f

Ответы [ 2 ]

3 голосов
/ 19 апреля 2020

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

  • значение сохраняется в объекте без объявленного типа через lvalue, имеющий тип, который не является символьным типом,
  • последующие обращения, которые не изменяют сохраненное значение
  • значение копируется в объект, не имеющий объявленного типа, используя memcpy или memmove
  • или копируется как массив символов type

Таким образом, оставшийся регистр «все чтение и запись»:

  • значение сохраняется в объекте, у которого нет объявленного типа, через lvalue, имеющий тип, который - это тип символа,
  • любые другие записи, о которых мы не думали

Во второй части код является правильным в соответствии с текстом C11 согласно :

Если значение сохраняется в объекте, у которого нет объявленного типа, через lvalue, имеющий тип, который не является символьным типом, то тип lvalue становится эффективным типом объекта для этот доступ

*x = 8; сохраняет значение в объекте, у которого нет объявленного типа, через lvalue, имеющий тип, который не является символьным типом. Таким образом, эффективный тип объекта для этого доступа - int, а затем в 6.5 / 7 мы имеем объект эффективного типа int, доступ к которому осуществляется по lvalue типа int. То же самое относится и к *f = 20.5 с float вместо int.

Сноска: есть много причин полагать, что текст 6.5 / 6 и / 7 является дефектным , как вы увидели при поиске других вопросов в топи c. Люди (и авторы компиляторов) формируют свою собственную интерпретацию правила.

0 голосов
/ 21 апреля 2020

Насколько я могу судить, среди членов комитета никогда не было единодушного понимания того, что правила "эффективного типа" должны означать во всех угловых случаях; любая правдоподобная интерпретация либо запрещает то, что должно быть полезной оптимизацией, не учитывает то, что должно быть пригодными для использования конструкциями, либо и то и другое. Насколько я могу судить, ни один компилятор, который был бы столь же «строгим», как clang и g cc, правильно обрабатывает все угловые случаи, изложенные в правилах, в соответствии с любой разумной интерпретацией Стандарта.

struct s1 { char x[1]; };
struct s2 { char x[1]; };

void convert_p_to_s1(void *p)
{
    int q = ((struct s2*)p)->x[0]+1;
    ((struct s1*)p)->x[0] = q-1;
}

int test(struct s1 *p1, struct s2 *p2)
{
    p1->x[0] = 1;
    p2->x[0] = 2;
    convert_p_to_s1(p1);
    return p1->x[0];
}

Ни лязг, ни g cc не допускают возможности того, что test может записать элемент x[0] из struct s1 в местоположение, а затем записать это же местоположение, используя элемент x[0] из struct s2, затем читать, используя x[0] из struct s2, писать, используя x[0] из struct s1, а затем читать, используя x[0] из struct s1, причем все операции чтения и записи выполняются с помощью разыменования указателей типа char* и с каждым чтением lvalue, полученного из указателя структуры, которому предшествует запись этого хранилища lvalue, полученным таким же образом из указателя того же типа.

До C99 это было довольно Многие признают, что качественные реализации должны воздерживаться от применения правил доступа типов таким образом, чтобы это наносило ущерб их клиентам, независимо от Будет ли Стандарт требовать такой сдержанности. Поскольку некоторые реализации использовались для целей, которые требовали странного доступа к объектам, но не требовали причудливых оптимизаций, в то время как другие использовались для целей, которые не требовали сложного доступа к хранилищу, но требовали дополнительной оптимизации, вопрос именно тогда, когда реализации должны признать, что доступ к одному объекту может повлиять на другой, был оставлен как вопрос качества реализации.

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

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

...