Страницы

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

четверг, 5 декабря 2019 г.

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

#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 стандарта.

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

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