gpt4 book ai didi

c++ - 更改数据时 UB 的说明

转载 作者:行者123 更新时间:2023-11-28 03:16:43 28 4
gpt4 key购买 nike

我试图向一个工作伙伴证明,如果真的想要(并且知道如何),你可以通过使用一些技巧来改变常量限定变量的值,在我的演示过程中,我发现存在两个常量值的“ flavor ”:无论您做什么都无法改变的 flavor ,以及可以通过使用肮脏的技巧来改变的 flavor 。

当编译器使用文字值而不是存储在堆栈中的值时,常量值是不可更改的(读作here),这里是piece of code这表明了我的意思:

// 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 数据的值)预计该程序行为怪异;但这种怪异超出了我的预期。

假设编译器正在使用字面量值,那么,当代码到达改变常量值的指令时(通过引用、指针或memcpying),简单地忽略顺序只要该值是文字(虽然是未定义的行为)。这解释了为什么值保持不变但是:

  • 为什么两个变量中的内存地址相同但包含的值不同?

AFAIK 相同的内存地址不能指向不同的值,因此,其中一个输出是谎言:

  • 到底发生了什么?哪个内存地址是假的(如果有的话)?

对上面的代码进行一些更改,我们可以尝试避免使用字面值,这样诡计就会起作用(source here):

// 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 数据用作文字而不是值时,内存地址的行为就会显现出来。

因此,为了确保这个假设,我做了最后一个测试,将 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 ( code here ) 相同,所以我认为数据是作为变量而不是文字值传递的,因为它用作参数,所以这让我奇怪:

  • 是什么让编译器决定将 const 值优化为文字值?

简而言之,我的问题是:

  • 测试 1 中。
    • 为什么 const 值和 no-const 值共享相同的内存地址但其包含的值不同?
    • 程序执行了哪些步骤来生成此输出?哪个内存地址是假的(如果有的话)?
  • 测试 3
    • 是什么让编译器决定将 const 值优化为文字值?

最佳答案

一般来说,分析未定义行为是没有意义的,因为不能保证您可以将分析结果转移到不同的程序。

在这种情况下,可以通过假设编译器应用了称为 constant propagation 的优化技术来解释该行为。 .在该技术中,如果您使用编译器知道其值的 const 变量的值,则编译器会将 const 变量的使用替换为该变量的值变量(在编译时已知)。变量的其他用途(例如获取其地址)不会被替换。

此优化是有效的,正是因为更改定义为 const 的变量会导致未定义的行为,并且允许编译器假定程序不会调用未定义的行为.

因此,在 TEST 1 中,地址相同,因为它们都是相同的变量,但值不同,因为每一对中的第一个反射(reflect)了编译器(正确地)假定的内容变量的值和第二个反射(reflect)了实际存储在那里的内容。在 TEST 2TEST 3 中,编译器无法进行优化,因为编译器不能 100% 确定函数参数将引用常量值(在 TEST 2 中,它没有)。

关于c++ - 更改数据时 UB 的说明,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16668656/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com