Я пытался продемонстрировать коллеге по работе, что вы можете изменить значение переменной с постоянной константой, если действительно хотите (и умеете), с помощью некоторой хитрости, во время моей демонстрации я обнаружил, что существует два «аромата» постоянных значений: те, которые вы не можете изменить, что бы вы ни делали, и те, которые вы можете изменить, используя грязные трюки.
Постоянное значение невозможно изменить, когда компилятор использует литеральное значение вместо значения, хранящегося в стеке (прочитанные Вот), вот кусок кода это показывает, что я имею в виду:
// TEST 1
#define LOG(index, cv, ncv) std::cout \
<< std::dec << index << ".- Address = " \
<< std::hex << &cv << "\tValue = " << cv << '\n' \
<< std::dec << index << ".- Address = " \
<< std::hex << &ncv << "\tValue = " << ncv << '\n'
const unsigned int const_value = 0xcafe01e;
// Try with no-const reference
unsigned int &no_const_ref = const_cast<unsigned int &>(const_value);
no_const_ref = 0xfabada;
LOG(1, const_value, no_const_ref);
// Try with no-const pointer
unsigned int *no_const_ptr = const_cast<unsigned int *>(&const_value);
*no_const_ptr = 0xb0bada;
LOG(2, const_value, (*no_const_ptr));
// Try with c-style cast
no_const_ptr = (unsigned int *)&const_value;
*no_const_ptr = 0xdeda1;
LOG(3, const_value, (*no_const_ptr));
// Try with memcpy
unsigned int brute_force = 0xba51c;
std::memcpy(no_const_ptr, &brute_force, sizeof(const_value));
LOG(4, const_value, (*no_const_ptr));
// Try with union
union bad_idea
{
const unsigned int *const_ptr;
unsigned int *no_const_ptr;
} u;
u.const_ptr = &const_value;
*u.no_const_ptr = 0xbeb1da;
LOG(5, const_value, (*u.no_const_ptr));
Это дает следующий вывод:
1.- Address = 0xbfffbe2c Value = cafe01e
1.- Address = 0xbfffbe2c Value = fabada
2.- Address = 0xbfffbe2c Value = cafe01e
2.- Address = 0xbfffbe2c Value = b0bada
3.- Address = 0xbfffbe2c Value = cafe01e
3.- Address = 0xbfffbe2c Value = deda1
4.- Address = 0xbfffbe2c Value = cafe01e
4.- Address = 0xbfffbe2c Value = ba51c
5.- Address = 0xbfffbe2c Value = cafe01e
5.- Address = 0xbfffbe2c Value = beb1da
Поскольку я полагаюсь на UB (изменить значение постоянных данных) ожидается, что программа действует странно; но эта странность больше, чем я ожидал.
Предположим, что компилятор использует буквальное значение, а затем, когда код достигает инструкции, чтобы изменить значение константы (по ссылке, указателю или memcpy
ing), просто игнорирует порядок, пока значение является литералом (хотя поведение не определено). Это объясняет, почему значение остается неизменным, но:
AFAIK один и тот же адрес памяти не может указывать на разные значения, поэтому один из выходов лежит:
Сделав несколько изменений в приведенном выше коде, мы можем попытаться избежать использования литерального значения, чтобы хитрость выполнила свою работу (источник здесь):
// TEST 2
// Try with no-const reference
void change_with_no_const_ref(const unsigned int &const_value)
{
unsigned int &no_const_ref = const_cast<unsigned int &>(const_value);
no_const_ref = 0xfabada;
LOG(1, const_value, no_const_ref);
}
// Try with no-const pointer
void change_with_no_const_ptr(const unsigned int &const_value)
{
unsigned int *no_const_ptr = const_cast<unsigned int *>(&const_value);
*no_const_ptr = 0xb0bada;
LOG(2, const_value, (*no_const_ptr));
}
// Try with c-style cast
void change_with_cstyle_cast(const unsigned int &const_value)
{
unsigned int *no_const_ptr = (unsigned int *)&const_value;
*no_const_ptr = 0xdeda1;
LOG(3, const_value, (*no_const_ptr));
}
// Try with memcpy
void change_with_memcpy(const unsigned int &const_value)
{
unsigned int *no_const_ptr = const_cast<unsigned int *>(&const_value);
unsigned int brute_force = 0xba51c;
std::memcpy(no_const_ptr, &brute_force, sizeof(const_value));
LOG(4, const_value, (*no_const_ptr));
}
void change_with_union(const unsigned int &const_value)
{
// Try with union
union bad_idea
{
const unsigned int *const_ptr;
unsigned int *no_const_ptr;
} u;
u.const_ptr = &const_value;
*u.no_const_ptr = 0xbeb1da;
LOG(5, const_value, (*u.no_const_ptr));
}
int main(int argc, char **argv)
{
unsigned int value = 0xcafe01e;
change_with_no_const_ref(value);
change_with_no_const_ptr(value);
change_with_cstyle_cast(value);
change_with_memcpy(value);
change_with_union(value);
return 0;
}
Который производит следующий вывод:
1.- Address = 0xbff0f5dc Value = fabada
1.- Address = 0xbff0f5dc Value = fabada
2.- Address = 0xbff0f5dc Value = b0bada
2.- Address = 0xbff0f5dc Value = b0bada
3.- Address = 0xbff0f5dc Value = deda1
3.- Address = 0xbff0f5dc Value = deda1
4.- Address = 0xbff0f5dc Value = ba51c
4.- Address = 0xbff0f5dc Value = ba51c
5.- Address = 0xbff0f5dc Value = beb1da
5.- Address = 0xbff0f5dc Value = beb1da
Как мы видим, переменная с определением const была изменена на каждом change_with_*
вызов, и поведение такое же, как и раньше, за исключением этого факта, поэтому я хотел предположить, что странное поведение адреса памяти проявляется, когда данные const используются в качестве литерала вместо значения.
Итак, чтобы убедиться в этом, я сделал последний тест, изменив unsigned int value
в main
в const unsigned int value
:
// TEST 3
const unsigned int value = 0xcafe01e;
change_with_no_const_ref(value);
change_with_no_const_ptr(value);
change_with_cstyle_cast(value);
change_with_memcpy(value);
change_with_union(value);
Удивительно, но результат такой же, как TEST 2
(код здесь), поэтому я предполагаю, что данные передаются как переменные, а не как буквальные значения из-за их использования в качестве параметра, поэтому меня удивляет:
Вкратце, мои вопросы:
TEST 1
,
TEST 3
В общем, анализировать неопределенное поведение бессмысленно, потому что нет никакой гарантии, что вы можете перенести результаты своего анализа в другую программу.
В этом случае поведение можно объяснить, если предположить, что компилятор применил метод оптимизации, называемый постоянное распространение. В этой технике, если вы используете значение const
переменная, для которой компилятор знает значение, то компилятор заменяет использование const
переменная со значением этой переменной (как это известно во время компиляции). Другие варианты использования переменной, такие как получение ее адреса, не заменяются.
Эта оптимизация действительна именно потому, что изменение переменной, которая была определена как const
приводит к неопределенному поведению, и компилятор может предполагать, что программа не вызвать неопределенное поведение.
Итак, в TEST 1
адреса одинаковы, потому что это все одна и та же переменная, но значения отличаются, потому что первая из каждой пары отражает то, что компилятор предполагает (справедливо), чтобы быть значением переменной, а вторая отражает то, что на самом деле там хранится.
В TEST 2
а также TEST 3
компилятор не может выполнить оптимизацию, потому что компилятор не может быть на 100% уверен, что аргумент функции будет ссылаться на постоянное значение (и в TEST 2
нет)
Других решений пока нет …