数据变化时UB的解释
Explanation of the UB while changing data
我试图向一个工作伙伴演示,如果你真的想(并且知道如何),你可以通过使用一些技巧来改变常量限定变量的值,在我的演示中,我发现存在两种"口味"的常量值:一种是你无法改变的,另一种是你可以通过使用肮脏的技巧来改变的。
当编译器使用文字值而不是存储在堆栈上的值时,常量值是不可更改的,这里有一段代码显示了我的意思:
// 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(更改const data的值),预计程序会表现得很奇怪;但这奇怪的事情比我想象的要多。
假设编译器正在使用文字值,那么,当代码到达改变常量值的指令时(通过引用、指针或memcpy
),只要值是文字值,就忽略顺序(尽管是未定义的行为)。这解释了为什么值保持不变,但是:
- 为什么是相同的内存地址在两个变量,但包含的值不同?
同样的内存地址不能指向不同的值,所以,其中一个输出是谎言:
- 到底发生了什么?哪个内存地址是假的(如果有的话)?
// 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
正如我们所看到的,每次change_with_*
调用时,const限定变量都被更改了,除了这个事实之外,其行为与以前相同,所以我很想假设当const数据被用作文字而不是值时,内存地址的奇怪行为就会表现出来。
main
中的unsigned int value
改为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
(这里的代码)相同,所以我认为数据是作为变量传递的,而不是作为文字值,因为它被用作参数,所以这让我想知道:
- 什么事情使编译器决定将const值优化为文字值?
总之,我的问题是:
- 在
TEST 1
。- 为什么const值和非const值共享相同的内存地址,但其包含的值不同?
- 程序产生此输出的步骤是什么?哪个内存地址是假的(如果有的话)?
在 - 什么事情使编译器决定将const值优化为文字值?
TEST 3
一般来说,分析未定义行为是没有意义的,因为不能保证您可以将分析结果转移到另一个程序中。
在这种情况下,可以通过假设编译器应用了称为常量传播的优化技术来解释该行为。在这种技术中,如果您使用编译器知道其值的const
变量的值,那么编译器将用该变量的值替换const
变量的使用(因为它在编译时是已知的)。该变量的其他用途,如获取其地址,不会被替换。
这个优化是有效的,因为改变一个定义为const
的变量会导致未定义行为,编译器被允许假设程序没有调用未定义行为。
因此,在TEST 1
中,地址是相同的,因为它都是相同的变量,但是值不同,因为每对中的第一个反映了编译器假定(正确地)是变量的值,第二个反映了实际存储在那里的值。在TEST 2
和TEST 3
中,编译器无法进行优化,因为编译器不能100%确定函数参数将引用常量值(而在TEST 2
中,它没有)。
- 请解释"函数1(p1,p2,p3);"的输出
- 请解释这句话(cout<<1+int((a<b)^((b-a)&1) )<<endl
- 被解释为低级别const的const对象的地址
- 计算每个节点的树高,帮助我解释这个代码解决方案
- MSVC将仅移动结构参数解释为指针
- 内联程序集printf将整数解释为地址
- 有人能解释一下为什么下界是这样工作的吗C++的
- Visual Studio(或任何其他工具)能否将地址解释为调用堆栈(boost上下文)的开头
- 我是c ++的新手,你能解释一下在这种情况下的指针吗
- 有人能为我解释一下C++代码吗
- 你能解释一下什么运行时错误是如何解决它的吗?
- 'string.assign(string.data(), 5)' 是明确定义的还是 UB?
- 请解释字谜的代码,我看不懂计数器数组,每个值已经是0
- 有人可以向我解释为什么控制台输出 0 吗?
- 有人可以解释一下这段代码如何能够反转字符串
- 在 C++ 中解释多维向量的语句时感到困惑
- 使用 CTRP 时,是否访问访问父构造函数 UB 中的子属性?
- 解释一下 for (char c : str) 的作用?
- 如何在不引起UB的情况下将字节序列重新解释为POD结构
- 数据变化时UB的解释