Подскажите, действительно ли в Стандарте сказано, что обходное изменение 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 стандарта.
Комментариев нет:
Отправить комментарий