在C++中更改常量变量的值

17

我试图更改一个被定义为int const的变量的值,如下所示。

const int w = 10;
int* wp = const_cast <int*> (&w);
*wp = 20;

尽管看起来w和wp指向同一内存地址,但赋值后w的值没有改变,仍为10。但如果在声明时按以下方式定义,则可以更改w的值。

int i = 10;
const int w = i;

如果我将i的声明更改为const,就像这样:

const int i = 10;

w的值没有改变。

在第一种情况下,尽管w和wp指向同一内存位置(当我打印它们的地址时得到这种印象),但w的值为什么没有更改呢?

编译器如何处理这两种情况有何不同之处?

有什么办法可以确保无论如何定义,w都不会失去constness吗?


11
如果一个对象最初被创建为const,那么你不应该把它用const_cast转化成非const类型,这是未定义的行为。如果一个对象最初是作为非const类型创建的,你可以随意使用const_cast来进行类型转换。 - C. K. Young
7
在某些实现中,如果const对象的内容位于只读区域(例如.rodata而不是非const数据所在的.data),写入该对象可能会导致程序崩溃。请注意,此处的翻译已经尽可能保证通俗易懂且不改变原意。 - C. K. Young
6
似乎没有人问过一个显而易见的问题——如果你想改变这个值,为什么要将这个变量标记为“const”? - Graeme Perrow
3
@Graeme:不,原帖的作者希望找到一种方法使const属性不能被移除,即保证人们无法使用const_cast从他们的对象中移除const属性。这在问题的最后一行中有所说明。 - C. K. Young
2
我并不是想改变这个值,我只是想理解其中的差异,并且想知道如何禁止他人使用我的程序。我试图添加更长的注释,但似乎它已经超出了注释应有的长度。我会重新表述并添加注释。 - Narendra N
显示剩余5条评论
8个回答

16

这是const cast未定义的情况之一,因为代码可能被优化,以使w不真正成为一个变量,并且在编译后的代码中不存在。

请尝试以下操作:

const volatile int w = 10; 
int &wr = const_cast <int &> (w); 
wr = 20; 
std::cout << w << std::endl;

不管怎样,我不建议这样滥用const_cast。


3
其中的魔法差异在于 volatile 关键字。如果将其移除,那么变量 w 的行为就像 Narendra 原始基于指针的示例中一样等于 10。 - Paul Stephenson
1
你是正确的,volatile限定符用于防止编译器优化掉w。 - rmn
我已经在三个不同的编译器上尝试过相同的代码 [Visual C++ Express Edition、g++ 和 Sun CC 编译器],在所有三个编译器上结果都是一样的。所有编译器的结果都保持一致,因此造成了困惑。顺便说一下,我试过使用 volatile,它按照预期工作。 - Narendra N
我的一位同事提到,只有在编译时已知值的情况下,变量才会保持不变。当定义为int i = 10; int const w = i;时,由于i不是const,我们可以在定义w之前通过从用户读取值来更改它的值。如果将值分配给const变量使用文字或const变量,则该值将在编译时已知并保持不变。也许const_cast的目的就像Chris Jester所评论的那样,是为了将那些被创建为非const的变量进行const_cast转换,而不是反过来。 - Narendra N
1
所有对常量对象的写操作都是未定义的。不确定 volatile 示例想要展示什么,它仍然是未定义的。 - M.M

8
上面示例中的代码将被翻译为以下汇编语言代码:
    movl    $10, 28(%esp)  //const int i = 10; 
    leal    28(%esp), %eax //int* wp = const_cast <int*>(&i);
    movl    %eax, 24(%esp) //store the pointer on the stack
    movl    24(%esp), %eax //place the value of wp in eax
    movl    $20, (%eax) //*wp  = 20; -  so all good until here
    movl    $10, 4(%esp) //place constant value 10 onto the the stack for use in printf
    movl    $.LC0, (%esp) // load string
    call    printf //call printf

因为原始的int i被声明为常量,编译器有权使用字面值而不是存储在堆栈上的值。这意味着该值不会改变,您将被困于原始值10。
故事的寓意是编译时常量应保持不变,因为这就是您告诉编译器的。故事的寓意是为了改变常量而强制转换去除const限定符可能会导致糟糕的结果。

5

const_cast不能消除变量本身的常量属性。如果你想把一个非常量变量通过引用传递给一个带有常量引用参数的方法,例如 void foo(const int& x),那么你可以使用const_cast来修改foox的值,但前提是你传入的变量本身不是常量。


2
抱歉,我只能根据您提供的文本进行回答。请您提供需要翻译的内容。
const int w = 10;
int* wp = const_cast <int*> (&w);
*wp = 20;
// some code

介绍下同名的不同常量

const int w = 10;
{
   const int w = 20;
   // the same code
}

如果“new”常量应该依赖于它自己的值,您应该引入另一个常量(const int _w = w; const int w = _w * 2;)。不必要的赋值将被编译器优化掉--因为我们已经看到它做了这样的优化,这也是您提出问题的原因。

1
дё‘йҷӢзҡ„const_castе’ҢжұҮзј–д»Јз ҒпјҢйҮҚж–°з»‘е®ҡеёёйҮҸжҳҜдёҖз§ҚжӣҙиҮӘ然зҡ„еӨ„зҗҶж–№ејҸгҖӮ - P Shved

2

您不应该更改const值。它是const的原因,尝试更改它很可能只会导致错误。如果const存储在只读内存部分中,则会出现访问冲突。


1
很好,这也是我的第一反应。但在现实世界中,有时候你必须这样做。"它是const的理由"至少表明最初将其设为const的程序员知道自己在做什么。 - John Dibling
@John:即使在现实世界中,也没有理由更改常量值。修复潜在问题而不是对其进行操作。 - Martin York

1

这里是一个复习,需要注意的是这是在C语言中。使用const关键字时,变量或指针的使用具有欺骗性的棘手基础知识。这突显了指针变量foo和如何使用该关键字可以改变其含义之间的区别。

char const *foo;
char * const foo;
const char *foo;

上述第一和最后一个声明使得“foo”所指向的数据为只读,但是你可以更改“foo”所指向的地址,例如:

const *char foo; /* 或者 char const *foo */
char str[] = "Hello";
foo = &str[0]; /* 没问题! */
foo[1] = 'h'; /* BZZZZTTT! 编译失败! */

上述中间声明使得指针为只读,即你不能更改“foo”所指向的数据的地址。

char * const foo;
char str[] = "Hello";
foo = &str[0]; /* BZZZZTTT! 编译失败! */

const *char foo; 是一个错误。 - M.M
char *const foo; 是 C++ 中的一个错误(此问题标记为 C++) - const 变量必须有一个初始化器。 - M.M
是的,我在寻找类似问题的重复时遇到了这个帖子。 - M.M

1

好问题。我觉得混淆的原因在于C++在不同的上下文中使用'const'关键字表示两个不同的概念。这些概念分别是常量和只读变量。

当一个'const'变量的值可以在编译期间计算出来时,它创建了一个真正的常量。对这种常量的引用会在使用时被替换为其值。这就是为什么没有内存位置可以被改变以影响到所有使用它的地方。这就像使用#define一样。

当一个'const'变量的值无法在编译期间计算出来时,它创建了一个只读变量。它在内存中有一个位置,包含一个值,但编译器强制实施只读行为。


0

我的猜测是声明w为const可以让编译器执行更激进的优化,例如内联w的值和重新排序指令。 w是否看起来发生了变化取决于在具体情况下应用了哪些优化,而这不在您的控制范围内。

您无法强制w完全成为const。 cons_cast应该是提示程序员可能正在做一些可疑操作的提示。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接