Страницы

Поиск по вопросам

четверг, 11 октября 2018 г.

Язык C, UB при изменении const

Подскажите, действительно ли в Стандарте сказано, что обходное изменение const объекта - это неопределенное поведение? Я попытался найти эту информацию самостоятельно, но мне не удалось. И если это действительно так (в чем я почти уверен), то я не совсем понимаю, в каких случаях применимо это правило.
Например, даже в таком простом примере не все очевидно:
const int a = 1; int *b = (int*)&a;// Это уже UB или еще нет? *b = 2;// Это точно UB?
Относительно такого я не могу сделать каких-либо предположений:
const int *a = malloc(sizeof(int)); int *b = (int*)a;// UB? *b = 1;// UB?
Это тоже не ясно:
int i = 1; const int *a = &i; // Может, на следующей строке компилятор посчитает, что объект, // на который указывает "a", является константным? int *b = (int*)a; *a = 2;// И эта операция приведет к UB?
И это:
// Абсолютно необходимая функция... void f(void *const _p) { void **p = (void**)&_p; *p = NULL; } Часто встречал в код-гайдах компаний требования использовать const везде, где это только возможно. Например, имеется функция, которая помещает указатель на данные в связный список. Поскольку функция не собирается изменять данные или указатель на данные, а так же указатель на сам контейнер, то обычно функция выглядит так:
int list_push_front(list *const _list, const void *const _data) { // ...
// И приходится делать так... new_node->data = (void*)_data;
// ... }
А ведь мы всего-лишь хотели сказать, что функция не меняет вставляемые в список данные, а так же не меняет входные параметры-указатели, защищая проект от некоторых видов ниндзя-багов
Так можно делать? Будет ли это действительно хорошим тоном, или последующее приведение (void*) является UB?


Ответ

Стандарт C11 6.7.3/6:
If an attempt is made to modify an object defined with a const-qualified type through use of an lvalue with non-const-qualified type, the behavior is undefined.

int *b = &a;// Это уже UB или еще нет?
Модификации пока еще нет, есть только присваивание несовместимых типов указателей. Это нарушение ограничений (constraint violation), но ещё не UB.
В стандарте есть похожий пример (6.5.16.1/6):
const char **cpp; char *p; const char c = 'A'; cpp = &p; //constraint violation *cpp = &c; //valid *p = 0; //valid
The first assignment is unsafe because it would allow the following valid code to attempt to change the value of the const object c
Сказано, что присваивание небезопасно (unsafe), т.к. позволяет менять константное значение (о чем выше сказано, что это UB), но само преобразование не даёт UB.
Чуть более подробно:
cpp = &p; // сохраняем в cpp адрес указателя p *cpp = &c; // записываем в p адрес константы c *p = 0; // попытка обнулить константу c (UB)
Если вывести после этого кода значения *p и c, можно увидеть, например, следующее:
gcc: 0 0 clang 0 65
Типичные результаты для неопределённого поведения. В первом случае константа обнулилась, во втором случае по сути одинаковые данные имеют разные значения.

const int *a = malloc(sizeof(int));
Выражение справа не является константым, т.е. не будь здесь левой части, память, выделенная malloc доступна для модификации, а значит косвенное её изменение не есть изменение константного объекта, т.е. допустимо. Это моё размышление, надо смотреть стандарт более подробно.

Преобразование из п.3 вашего вопроса по сути не отличается от ситуации в п.1. Мы просто преобразуем указатель для хранения. В дальнейшем, если исходные данные изменяемы (не объявлены как const), то по новому указателю эти данные можно менять, иначе получим UB.
Кстати, список всех UB приведён в разделе J.2 Undefined behavior стандарта.

Комментариев нет:

Отправить комментарий