我在维基百科页面上看到Null_pointer Bjarne Stroustrup建议将NULL定义为
const int NULL = 0;
如果“你觉得你必须定义NULL”。我立刻想到了,嘿..等一下,怎么样const_cast?
经过一些实验,我发现了
int main() {
const int MyNull = 0;
const int* ToNull = &MyNull;
int* myptr = const_cast<int*>(ToNull);
*myptr = 5;
printf("MyNull is %d\n", MyNull);
return 0;
}
会打印“MyNull为0”,但是如果我让const int属于一个类:
class test {
public:
test() : p(0) { }
const int p;
};
int main() {
test t;
const int* pptr = &(t.p);
int* myptr = const_cast<int*>(pptr);
*myptr = 5;
printf("t.p is %d\n", t.p);
return 0;
}
然后打印“t.p为5”!
为什么两者之间存在差异?为什么“* myptr = 5;”在我的第一个例子中默默地失败了,如果有的话,它正在执行什么动作?
答案 0 :(得分:4)
首先,您通过尝试修改常量变量来调用两种情况下的未定义行为。
在第一种情况下,编译器看到MyNull
被声明为常量,并用0
替换main()中对它的所有引用。
在第二种情况下,由于p
在一个类中,编译器无法确定它只能用classInstance.p
替换所有0
,因此您可以看到修改的结果
答案 1 :(得分:3)
首先,第一种情况发生的情况是编译器最有可能翻译你的
printf("MyNull is %d\n", MyNull);
立即进入
printf("MyNull is %d\n", 0);
因为它知道const
个对象永远不会在有效程序中更改。您尝试更改const
对象会导致未定义的行为,这正是您所观察到的。因此,从实际的角度来看,忽略未定义的行为一秒,您的*myptr = 5
很可能成功修改了Null
。只是你的程序并不真正关心你现在Null
中的内容。它知道Null
为零并且始终为零并且相应地起作用。
其次,为了根据您所指的建议定义NULL
,您必须将其明确定义为积分常量表达式(ICE)。你的第一个变种确实是ICE。你的第二个变种不是。 ICE中不允许使用类成员访问,这意味着您的第二个变体与第一个变体有很大不同。第二个变体不会为NULL
生成可行的定义,即使将test::p
声明为const int
并设置为零,也无法使用SomeType *ptr1 = Null; // OK
test t;
SomeType *ptr2 = t.p; // ERROR: cannot use an `int` value to initialize a pointer
初始化指针
const
对于第二种情况中的不同输出...未定义的行为是未定义的行为。这是不可预测的。从实际的角度来看,你的第二个上下文比较复杂,所以编译器无法从上面的优化中做出预测。即你确实成功地突破了语言级限制并修改了一个const限定变量。语言规范不会让编译器轻松(或可能)优化类的p
成员,因此在物理层面const_cast
只是驻留在内存中的类的另一个成员,在该类的每个对象中。你的黑客只是修改了那个记忆。但它并不合法。行为仍然未定义。
当然,这一切都是毫无意义的。看起来这一切都始于“const_cast
”问题。那么,它呢? const
从未打算用于此目的。您不能修改const_cast
个对象。使用const_cast
或没有{{1}} - 无关紧要。
答案 2 :(得分:1)
您的代码正在修改声明为常量的变量,因此任何事情都可能发生。讨论为什么某个事情发生而不是另一个事情是完全没有意义的,除非你讨论不可移植的编译器内部问题......从C ++的角度来看,代码根本没有任何意义。
关于const_cast
要理解的一件重要事情是const const不是为了搞乱变量声明的常量,而是关于引用和指针声明不变。
在C ++中,const int *
通常被理解为“指向常数整数的指针”,而此描述完全错误。对于编译器而言,它完全不同:“指针不能用于写入整数对象”。
这显然可能是一个微小的差异,但确实是一个巨大的差异,因为
“constness”是指针的属性,而不是指向对象的属性。
没有任何关于指向物体是否恒定的事实。
“常量”这个词与意义无关(这就是为什么我认为使用const
这是一个错误的命名选择)。 const int *
并不是在谈论任何事情的常数,只是关于“只读”或“读/写”。
const_cast
允许您在指针和可用于写入的引用和引用之间进行转换,因为它们是“只读”而不能。指向的对象永远不会成为这个过程的一部分,标准只是说在“抛弃”常量之后采用const指针并将其用于写入是合法的,但前提是指向对象尚未声明为常量。 / p>
指针和引用的常量永远不会影响将由编译器生成的机器代码(另一个常见的误解是,如果使用const引用和指针,编译器可以生成更好的代码,但这完全是假的...对于优化器,const引用和const指针只是一个引用和一个指针。
指针和引用的常量已经被引入以帮助程序员,而不是选择器(顺便说一下,我认为这对程序员的这种所谓的帮助也是值得怀疑的,但这是另一个故事)。
const_cast
是一种武器,可以帮助程序员使用破坏的指针和引用的常量声明(例如在库中),并使用破坏的引用和指针的常量概念(在mutable
之前)抛弃constness的例子是许多现实生活中唯一合理的解决方案。)
对const引用的误解也是一个非常常见的C ++反模式(甚至在标准库中使用)的基础,它表示传递const引用是一种传递值的智能方法。有关详细信息,请参阅this answer。