Разрешает ли стандарт C присваивать произвольное значение указателю и увеличивать его? - PullRequest
0 голосов
/ 28 июня 2018

Хорошо ли определено поведение этого кода?

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

int main(void)
{
    void *ptr = (char *)0x01;
    size_t val;

    ptr = (char *)ptr + 1;
    val = (size_t)(uintptr_t)ptr;

    printf("%zu\n", val);
    return 0;
}

Я имею в виду, можем ли мы присвоить некоторый фиксированный номер указателю и увеличить его, даже если он указывает на какой-то случайный адрес? (Я знаю, что вы не можете разыменовать его)

Ответы [ 5 ]

0 голосов
/ 29 июня 2018

Стандарт не требует, чтобы реализации обрабатывали преобразования целочисленных значений в указатели осмысленным образом для любых конкретных целочисленных значений или даже для любых возможных целочисленных значений, отличных от констант нулевых указателей. Единственное, что он гарантирует в отношении таких преобразований, - это то, что программа, которая сохраняет результат такого преобразования непосредственно в объект подходящего типа указателя и ничего с этим не делает, за исключением проверки байтов этого объекта, в худшем случае увидит неопределенные значения. Хотя поведение преобразования целого числа в указатель определяется реализацией, ничто не помешает любой реализации (независимо от того, что она на самом деле делает с такими преобразованиями!) От указания того, что некоторые (или даже все) байты для представления, имеющего значения Unspecified и указывающего, что некоторые (или даже все) целочисленные значения могут вести себя так, как будто они дают представления ловушек.

Единственные причины, по которым Стандарт вообще что-либо говорит о преобразованиях целых в указатель, заключаются в том, что:

  1. В некоторых реализациях конструкция имеет смысл, и некоторые программы для этих реализаций требуют этого.

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

  3. Было бы странно, если бы Стандарт описал конструкцию, но затем указал, что она имеет неопределенное поведение во всех случаях.

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

Я думаю, что было бы проще сказать, что любая операция, включающая в себя преобразования целочисленных значений в указатели с любыми значениями, отличными от значений intptr_t или uintptr_t, полученных из преобразований указателей в целые числа, вызывает неопределенное поведение, но затем следует отметить, что оно является общим для качественные реализации, предназначенные для низкоуровневого программирования для обработки неопределенного поведения «документированным образом, характерным для окружающей среды». Стандарт не определяет, когда реализации должны обрабатывать программы, которые вызывают UB таким образом, а вместо этого рассматривает это как проблему качества реализации.

Если реализация указывает, что преобразования целых чисел в указатели работают таким образом, который определяет поведение

char *p = (char*)1;
p++;

как эквивалент "char p = (char ) 2;", тогда реализация должна работать именно так. С другой стороны, реализация может определять поведение преобразования целых чисел в указатель таким образом, чтобы даже:

char *p = (char*)1;
char *q = p;  // Not doing any arithmetic here--just a simple assignment

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

0 голосов
/ 28 июня 2018

Это неопределенное поведение.

От N1570 (выделение добавлено):

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

Если значение является представлением ловушки, чтение его - неопределенное поведение:

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

И

Идентификатор является основным выражением при условии, что он был объявлен как обозначающий объект (в этом случае это lvalue ) или функцию (в этом случае это обозначение функции).

Следовательно, строка void *ptr = (char *)0x01; - это уже потенциально неопределенное поведение в реализации, где (char*)0x01 или (void*)(char*)0x01 - это представление ловушки. Левая часть - это выражение lvalue, которое не имеет символьного типа и читает представление прерывания.

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

0 голосов
/ 28 июня 2018

Назначение:

void *ptr = (char *)0x01;

Является определенным реализацией поведением , потому что оно преобразует целое число в указатель. Это подробно описано в разделе 6.3.2.3 стандарта C относительно указателей:

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

Что касается последующей арифметики указателей:

ptr = (char *)ptr + 1;

Это зависит от нескольких вещей.

Во-первых, текущее значение ptr может быть представлением прерываний согласно 6.3.2.3 выше. Если это так, поведение будет undefined .

Далее следует вопрос о том, указывает ли 0x1 действительный объект. Добавление указателя и целого числа допустимо только в том случае, если и операнд указателя, и результат указывают на элементы объекта массива (один объект считается массивом размера 1) или один элемент после объекта массива. Это подробно описано в разделе 6.5.6:

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

8 Когда выражение с целочисленным типом добавляется или вычитается из указателя, результат имеет тип указателя операнд. Если операнд-указатель указывает на элемент массива объект, и массив достаточно велик, результат указывает на элемент смещение от исходного элемента, так что разница нижние индексы полученных и исходных элементов массива равны целочисленное выражение. Другими словами, если выражение P указывает на i-й элемент массива, выражения (P) + N (эквивалентно, N + (P) ) и (P) -N (где N имеет значение n), указывающее на, соответственно i + n-й и i-n-й элементы объекта массива, если они существуют. Более того, если выражение P указывает на последний элемент объект массива, выражение (P) + 1 указывает на один последний элемент объект массива, и если выражение Q указывает на одну точку после последний элемент объекта массива, выражение (Q) -1 указывает на последний элемент объекта массива. Если оба указателя операнд и результат указывают на элементы одного массива объект, или один после последнего элемента объекта массива, оценка не должна приводить к переполнению; в противном случае поведение undefined. Если результат указывает на один элемент после последнего элемента объект массива, он не должен использоваться как операнд унарного * оператор, который оценивается.

В размещенной реализации значение 0x1 почти наверняка не указывает на действительный объект, в этом случае добавление undefined . Однако встроенная реализация может поддерживать установку указателей на конкретные значения, и в этом случае это может быть тот случай, когда 0x1 фактически указывает на действительный объект. Если да, то поведение четко определено , в противном случае оно равно undefined .

0 голосов
/ 28 июня 2018

Да, код четко определен как определенный реализацией. Это не неопределенно. См. ИСО / МЭК 9899: 2011 [6.3.2.3] / 5 и примечание 67.

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

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

0 голосов
/ 28 июня 2018

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

Результат void *ptr = (char*)0x01; определяется реализацией, отчасти из-за того, что char может иметь представление прерывания.

Но поведение следующей арифметики указателя в выражении ptr = (char *)ptr + 1; равно undefined . Это связано с тем, что арифметика указателей действительна только в массивах, включая один за концом массива. Для этого объект представляет собой массив длиной один.

...