Чтение превышает объект неопределенного поведения в C? - PullRequest
0 голосов
/ 16 мая 2018

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

#include <stdio.h>
#include <stdint.h>

int main() {
    int32_t a = 12;
    double *p = (double*)(&a);
    printf("%lf\n", *p);
    return 0;
}

Ответы [ 3 ]

0 голосов
/ 16 мая 2018

Это неопределенное поведение для C11 6.5 («строгое правило псевдонимов»):

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

В этом случае эффективным типом является int32_t (который является typedef, соответствующим чему-то вроде int или long).

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

double несовместим с int32_t, поэтому, когда код обращается к данным здесь: *p, он нарушает это правило и вызывает UB.

См. Что такоестрогое правило алиасинга? для деталей.

0 голосов
/ 16 мая 2018

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

Однако, даже игнорируя проблему типа lvalue, Стандарт не предъявляет никаких требований в отношении того, что происходит, если приложение пытается прочитать из памяти, которой оно не владеет. Здесь опять выбор поведения может иногда быть проблемой качества реализации. Здесь есть пять основных возможностей:

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

  2. Акт чтения может вести себя так, как будто он дает биты с Неуказанные значения, но не имеют другого побочного эффекта.

  3. Попытка чтения может завершить программу.

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

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

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

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

Кстати, на некоторых платформах есть еще одна проблема, которая может возникнуть, даже если, например, код использовал int[3] вместо одного int: выравнивание. На некоторых платформах значения определенных типов могут считываться или записываться только на определенные адреса, а некоторые адреса, которые подходят для меньших типов, могут не подходить для больших. На платформах, где int требуется 32-битное выравнивание, но double требует 64-битное выравнивание, учитывая int foo[3], компилятор может произвольно поместить foo, так что (double*)foo будет подходящим адресом для хранения double или так, чтобы (double*)(foo+1) было подходящим местом. Программист, знакомый с деталями реализации, может определить, какой адрес будет действительным, и использовать его, но код, который слепо предполагает, что адрес foo будет действительным, может завершиться ошибкой, если double имеет 64- требование выравнивания битов.

0 голосов
/ 16 мая 2018

С C99 Черновик комитета 6.5 Expressions, точка 7:

Доступ к сохраненному значению объекта должен быть доступен только через выражение lvalue, имеющее один из следующих типов: 76)
- тип, совместимый с действующим типом объекта,
- квалифицированная версия типа, совместимого с действительным типом объекта,
- тип, который является типом со знаком или без знака, соответствующимэффективный тип объекта,
- тип, который является типом со знаком или без знака, соответствующим квалифицированной версии действующего типа объекта,
- тип агрегирования или объединения, который включает один из вышеупомянутых типов средиего члены (включая, рекурсивно, член субагрегированного или содержавшего объединения) или
- тип символа.

Объект типа int имеет свой адрес, доступ к которому осуществляется с использованием выражения lvalue double тип.Типы int и double в любом случае несовместимы, они не агрегированы и double не является символьным типом.Разыменование указателя (выражения lvalue) типа double, указывающего на объект с типом int, является неопределенным поведением.Такие операции называются строгим нарушением алиасинга .

...