#c #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?
Ответы
Ответ 1
Стандарт 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 стандарта.
Комментариев нет:
Отправить комментарий