Яка семантика об'єктів, що перекриваються в С?


25

Розглянемо таку структуру:

struct s {
  int a, b;
};

Зазвичай 1 , ця структура матиме розмір 8 та вирівнювання 4.

Що робити, якщо ми створимо два struct sоб'єкти (точніше, запишемо у виділене сховище два такі об’єкти), причому другий об’єкт перекриє перший?

char *storage = malloc(3 * sizeof(struct s));
struct s *o1 = (struct s *)storage; // offset 0
struct s *o2 = (struct s *)(storage + alignof(struct s)); // offset 4

// now, o2 points half way into o1
*o1 = (struct s){1, 2};
*o2 = (struct s){3, 4};

printf("o2.a=%d\n", o2->a);
printf("o2.b=%d\n", o2->b);
printf("o1.a=%d\n", o1->a);
printf("o1.b=%d\n", o1->b);

Чи є щось щодо цієї програми невизначеною поведінкою? Якщо так, то де це стає невизначеним? Якщо це не UB, чи гарантовано завжди друкувати наступне:

o2.a=3
o2.b=4
o1.a=1
o1.b=3

Зокрема, я хочу знати, що відбувається з об'єктом, на який вказує, o1коли o2, що перекриває його, написано. Чи все ж дозволено отримувати доступ до незаблокованої частини ( o1->a)? Чи доступ до дозвоненої частини o1->bпросто такий самий, як і доступ o2->a?

Як тут застосовується ефективний тип ? Правила є досить чіткими, коли ви говорите про неперекриваються об'єкти та покажчики, які вказують на те саме місце, що і останнє сховище, але коли ви починаєте говорити про ефективний тип частин об'єктів або об'єктів, що перекриваються, менш зрозуміло.

Чи змінилось би щось, якби друге письмо було іншого типу? Якщо члени кажуть , intі shortзамість двох intроків?

Ось богбол, якщо ви хочете пограти з ним там.


1 Ця відповідь стосується платформ, де це теж не так: наприклад, деякі можуть мати розмір 4 та вирівнювання 2. На платформі, де розмір та вирівнювання були однаковими, це питання не застосовуватиметься, оскільки об'єкти, що перекриваються, вирівнюються бути неможливим, але я не впевнений, чи є така платформа.


2
Я майже впевнений, що це UB, але я дозволю мовному юристу надати розділ та вірш.
Бармар

Я думаю, що компілятор C у старих векторних системах Cray змусив вирівнювання та розмір бути однаковим, з моделлю ILP64 та вимушеним 64-бітовим вирівнюванням (адреси - це 64-бітні слова - без байтової адреси). Звичайно, це породило безліч інших проблем ....
Джон Д Маккалпін

Відповіді:


15

В основному це все сіра зона в стандарті; правило суворого псевдоніму вказує основні випадки і залишає читача (та постачальників компіляторів) заповнювати дані.

Були докладені зусилля для написання кращого правила, але поки вони не привели до жодного нормативного тексту, і я не впевнений, який це статус для C2x.

Як було сказано у моїй відповіді на попереднє запитання, найпоширенішим тлумаченням є те, що p->qзасоби (*p).qта ефективні типи застосовуються до всіх *p, хоча ми потім продовжуємо застосовувати .q.

Згідно з цією інтерпретацією, це printf("o1.a=%d\n", o1->a);може спричинити невизначеність поведінки, оскільки ефективного типу місцеположення *o1немає s(оскільки частина його була перезаписана).

Обґрунтування такої інтерпретації можна побачити у такій функції, як:

void f(s* s1, s* s2)
{
    s2->a = 5;
    s1->b = 6;
    printf("%d\n", s2->a);
}

З цією інтерпретацією останній рядок можна було б оптимізувати до puts("5");, але без нього компілятор повинен був би врахувати, що виклик функції, можливо, був f(o1, o2);і тому втрачає всі переваги, які нібито передбачені суворим правильним принципом числення.

Аналогічний аргумент стосується двох непов'язаних типів структури, у яких обидва випадково мають intчлен при різному зміщенні.


1
З f(s* s1, s* s2), без restrict, компілятор не може припустити s1і s2є різними вказівниками. Я думаю , знову ж таки без restrictцього не можна навіть припустити, що вони частково не перетинаються. IAC, я не бачу, що занепокоєння ОП добре демонструється f()аналогією. Успіхів непримітно. УФ для першої половини.
chux - Відновлення Моніки

@ chux-ReinstateMonica без обмежень s1 == s2буде дозволено, але не часткове перекриття. (Оптимізація в моєму прикладі коду все-таки може бути виконана, якщо s1 == s2)
ММ

@ chux-ReinstateMonica ви також можете розглянути ту саму проблему лише intзамість конструкцій (і системи з _Alignof(int) < sizeof(int)).
ММ

3
Статус такого роду питань щодо ефективного типу для C2x досить відкритий і все ще піддається дискусії в дослідницькій групі. Будьте обережні, хоча і вимагаєте еквівалентності p->qта (*p).q. Це може бути справедливо для інтерпретації типів, як ви заявляєте, але це неправда з операційної точки зору. При одночасному доступі до тієї самої структури важливо, щоб доступ члена не передбачав доступу будь-якого іншого члена.
Йенс Гуведт

Суворе правило дозволу стосується доступу . Лівий вираз у E1.E2виразі не здійснює доступу (я маю на увазі весь E1вираз. Деякі його підвираження можуть виконувати доступ. Тобто, якщо E1це так (*p), то читання значення вказівника при оцінці p- це доступ, але оцінка *pабо (*p)не виконує жодного доступ). Строге правило псевдоніму не застосовується у випадку, коли немає доступу.
юрист з мови
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.