简体   繁体   English

const_cast的未定义行为

[英]Undefined behaviour with const_cast

I was hoping that someone could clarify exactly what is meant by undefined behaviour in C++. 我希望有人能够准确地澄清C ++中未定义行为的含义。 Given the following class definition: 给定以下类定义:

class Foo
{
public:
    explicit Foo(int Value): m_Int(Value) { }
    void SetValue(int Value) { m_Int = Value; }

private:
    Foo(const Foo& rhs);
    const Foo& operator=(const Foo& rhs);

private:
    int m_Int;
};

If I've understood correctly the two const_casts to both a reference and a pointer in the following code will remove the const-ness of the original object of type Foo, but any attempts made to modify this object through either the pointer or the reference will result in undefined behaviour. 如果我已经正确理解了以下代码中的引用和指针的两个const_casts将删除Foo类型的原始对象的常量,但是通过指针或引用修改此对象的任何尝试都将导致未定义的行为。

int main()
{
    const Foo MyConstFoo(0);
    Foo& rFoo = const_cast<Foo&>(MyConstFoo);
    Foo* pFoo = const_cast<Foo*>(&MyConstFoo);

    //MyConstFoo.SetValue(1);   //Error as MyConstFoo is const
    rFoo.SetValue(2);           //Undefined behaviour
    pFoo->SetValue(3);          //Undefined behaviour

    return 0;
}

What is puzzling me is why this appears to work and will modify the original const object but doesn't even prompt me with a warning to notify me that this behaviour is undefined. 让我感到困惑的是为什么这似乎有效并将修改原始的const对象,但是甚至没有提示我发出警告通知我这种行为是未定义的。 I know that const_casts are, broadly speaking, frowned upon, but I can imagine a case where lack of awareness that C-style cast can result in a const_cast being made could occur without being noticed, for example: 从广义上讲,我知道const_casts是不受欢迎的,但是我可以想象一种情况是缺乏对C风格的强制转换可以导致const_cast的意识,而不会被注意到,例如:

Foo& rAnotherFoo = (Foo&)MyConstFoo;
Foo* pAnotherFoo = (Foo*)&MyConstFoo;

rAnotherFoo->SetValue(4);
pAnotherFoo->SetValue(5);

In what circumstances might this behaviour cause a fatal runtime error? 在什么情况下这种行为会导致致命的运行时错误? Is there some compiler setting that I can set to warn me of this (potentially) dangerous behaviour? 是否有一些编译器设置可以设置为警告我这种(可能)危险的行为?

NB: I use MSVC2008. 注意:我使用的是MSVC2008。

I was hoping that someone could clarify exactly what is meant by undefined behaviour in C++. 我希望有人能够准确地澄清C ++中未定义行为的含义。

Technically, "Undefined Behaviour" means that the language defines no semantics for doing such a thing. 从技术上讲,“未定义行为”意味着该语言没有定义用于执行此类操作的语义。

In practice, this usually means " don't do it ; it can break when your compiler performs optimisations, or for other reasons". 在实践中,这通常意味着“ 不要这样做 ;当编译器执行优化时,或者出于其他原因,它可能会中断”。

What is puzzling me is why this appears to work and will modify the original const object but doesn't even prompt me with a warning to notify me that this behaviour is undefined. 让我感到困惑的是为什么这似乎有效并将修改原始的const对象,但是甚至没有提示我发出警告通知我这种行为是未定义的。

In this specific example, attempting to modify any non-mutable object may "appear to work", or it may overwrite memory that doesn't belong to the program or that belongs to [part of] some other object, because the non-mutable object might have been optimised away at compile-time, or it may exist in some read-only data segment in memory. 在这个具体的例子中,尝试修改任何非可变对象可能“似乎工作”,或者它可能覆盖不属于该程序或属于某个其他对象的[部分]的内存,因为不可变对象可能在编译时已经过优化,或者可能存在于内存中的某些只读数据段中。

The factors that may lead to these things happening are simply too complex to list. 可能导致这些事情发生的因素太复杂而无法列出。 Consider the case of dereferencing an uninitialised pointer (also UB): the "object" you're then working with will have some arbitrary memory address that depends on whatever value happened to be in memory at the pointer's location; 考虑解除引用未初始化指针(也是UB)的情况:您正在使用的“对象”将具有一些任意内存地址,该地址取决于在指针位置的内存中发生的任何值; that "value" is potentially dependent on previous program invocations, previous work in the same program, storage of user-provided input etc. It's simply not feasible to try to rationalise the possible outcomes of invoking Undefined Behaviour so, again, we usually don't bother and instead just say " don't do it ". “价值”可能依赖于之前的程序调用,以前在同一程序中的工作,存储用户提供的输入等。尝试合理化调用未定义行为的可能结果是不可行的,所以,我们通常不会打扰而只是说“ 不要这样做 ”。

What is puzzling me is why this appears to work and will modify the original const object but doesn't even prompt me with a warning to notify me that this behaviour is undefined. 让我感到困惑的是为什么这似乎有效并将修改原始的const对象, 但是甚至没有提示我发出警告通知我这种行为是未定义的。

As a further complication, compilers are not required to diagnose (emit warnings/errors) for Undefined Behaviour, because code that invokes Undefined Behaviour is not the same as code that is ill-formed (ie explicitly illegal). 作为进一步的复杂化,编译器不需要诊断(发出警告/错误)未定义行为,因为调用未定义行为的代码与不正确的代码(即明确非法)不同。 In many cases, it's not tractible for the compiler to even detect UB, so this is an area where it is the programmer's responsibility to write the code properly. 在许多情况下,编译器甚至无法检测UB,因此编程人员有责任正确编写代码。

The type system — including the existence and semantics of the const keyword — presents basic protection against writing code that will break; 类型系统 - 包括const关键字的存在和语义 - 提供了基本保护,防止编写将被破坏的代码; a C++ programmer should always remain aware that subverting this system — eg by hacking away const ness — is done at your own risk, and is generally A Bad Idea.™ 一个C ++程序员应该始终意识到破坏这个系统 - 例如通过破解const - 是由你自己承担风险,并且通常是一个坏主意。

I can imagine a case where lack of awareness that C-style cast can result in a const_cast being made could occur without being noticed. 我可以想象这样一种情况,即缺乏对C风格演员可能会导致const_cast的意识,而不会被注意到。

Absolutely. 绝对。 With warning levels set high enough, a sane compiler may choose to warn you about this, but it doesn't have to and it may not. 在警告级别设置得足够高的情况下,理智的编译器可能会选择警告您这一点,但它没有必要,也可能没有。 In general, this is a good reason why C-style casts are frowned upon, but they are still supported for backwards compatibility with C. It's just one of those unfortunate things. 一般来说,这是为什么C风格的演员表示不满意的一个很好的理由,但它们仍然支持与C的向后兼容性。这只是其中一个不幸的事情。

Undefined behaviour depends on the way the object was born , you can see Stephan explaining it at around 00:10:00 but essentially, follow the code below: 未定义的行为取决于对象诞生的方式 ,您可以在00:10:00左右看到Stephan解释它,但实质上,请遵循以下代码:

void f(int const &arg)
{
    int &danger( const_cast<int&>(arg); 
    danger = 23; // When is this UB?
}

Now there are two cases for calling f 现在调用f有两种情况

int K(1);
f(k); // OK
const int AK(1); 
f(AK); // triggers undefined behaviour

To sum up, K was born a non const, so the cast is ok when calling f, whereas AK was born a const so ... UB it is. 总而言之, K出生时是一个非const,所以演员在调用f时是好的,而AK出生时是一个const所以... UB就是这样。

Undefined behaviour literally means just that: behaviour which is not defined by the language standard. 未定义的行为字面意思就是:行为不是由语言标准定义的。 It typically occurs in situations where the code is doing something wrong, but the error can't be detected by the compiler. 它通常发生在代码执行错误的情况下,但编译器无法检测到错误。 The only way to catch the error would be to introduce a run-time test - which would hurt performance. 捕获错误的唯一方法是引入运行时测试 - 这会损害性能。 So instead, the language specification tells you that you mustn't do certain things and, if you do, then anything could happen. 所以相反,语言规范告诉你,你不能做某些事情,如果你这样做,那么任何事情都可能发生。

In the case of writing to a constant object, using const_cast to subvert the compile-time checks, there are three likely scenarios: 在写入常量对象的情况下,使用const_cast来破坏编译时检查,有三种可能的情况:

  • it is treated just like a non-constant object, and writing to it modifies it; 它被视为非常量对象,写入它会修改它;
  • it is placed in write-protected memory, and writing to it causes a protection fault; 它被放置在写保护的存储器中,写入它会导致保护错误;
  • it is replaced (during optimisation) by constant values embedded in the compiled code, so after writing to it, it will still have its initial value. 它被嵌入在编译代码中的常量值替换(在优化期间),因此在写入之后,它仍将具有其初始值。

In your test, you ended up in the first scenario - the object was (almost certainly) created on the stack, which is not write protected. 在您的测试中,您最终进入了第一个场景 - 在堆栈上创建了对象(几乎可以肯定),该对象没有写保护。 You may find that you get the second scenario if the object is static, and the third if you enable more optimisation. 如果对象是静态的,您可能会发现第二种情况,如果启用了更多优化,则可能会获得第三种情况。

In general, the compiler can't diagnose this error - there is no way to tell (except in very simple examples like yours) whether the target of a reference or pointer is constant or not. 通常,编译器无法诊断此错误 - 无法判断引用或指针的目标是否为常量(除非像您这样的非常简单的示例)。 It's up to you to make sure that you only use const_cast when you can guarantee that it's safe - either when the object isn't constant, or when you're not actually going to modify it anyway. 你可以确保只在你能保证它是安全的时候才使用const_cast - 无论是在对象不是常数时,还是在你实际上还没有真正修改它的时候。

What is puzzling me is why this appears to work 令我困惑的是为什么这似乎有效

That is what undefined behavior means. 这就是未定义行为的含义。
It can do anything including appear to work. 它可以做任何事情,包括似乎工作。
If you increase your optimization level to its top value it will probably stop working. 如果将优化级别提高到最高值,它可能会停止工作。

but doesn't even prompt me with a warning to notify me that this behaviour is undefined. 但是甚至没有提示我发出警告通知我这种行为是未定义的。

At the point it were it does the modification the object is not const. 在这一点上它修改了对象不是 const。 In the general case it can not tell that the object was originally a const, therefore it is not possible to warn you. 在一般情况下,它无法判断该对象最初是一个const,因此无法警告您。 Even if it was each statement is evaluated on its own without reference to the others (when looking at that kind of warning generation). 即使是每个语句都是单独评估而不参考其他语句(在查看那种警告生成时)。

Secondly by using cast you are telling the compiler "I know what I am doing override all your safety features and just do it" . 其次,通过使用演员,你告诉编译器“我知道我在做什么覆盖你所有的安全功能,只是去做”

For example the following works just fine: (or will seem too (in the nasal deamon type of way)) 例如,以下工作正常:(或似乎也是如此(在鼻腔deamon类型的方式))

float aFloat;

int& anIntRef = (int&)aFloat;  // I know what I am doing ignore the fact that this is sensable
int* anIntPtr = (int*)&aFloat;

anIntRef  = 12;
*anIntPtr = 13;

I know that const_casts are, broadly speaking, frowned upon 我知道const_casts广泛地说是不受欢迎的

That is the wrong way to look at them. 这是看错它们的方法。 They are a way of documenting in the code that you are doing something strange that needs to be validated by smart people (as the compiler will obey the cast without question). 它们是一种在代码中记录您正在做一些需要由聪明人验证的奇怪事物的方法(因为编译器会毫无疑问地遵守演员表)。 The reason you need a smart person to validate is that it can lead to undefined behavior, but the good thing you have now explicitly documented this in your code (and people will definitely look closely at what you have done). 你需要一个聪明的人来验证的原因是它可能导致未定义的行为,但你现在已经在你的代码中明确地记录了这一点(人们肯定会密切关注你所做的事情)。

but I can imagine a case where lack of awareness that C-style cast can result in a const_cast being made could occur without being noticed, for example: 但是我可以想象一种情况,即缺乏对C风格演员可能会导致制作const_cast的意识可能会在没有被注意的情况下发生,例如:

In C++ there is no need to use a C style cast. 在C ++中,不需要使用C样式转换。
In the worst case the C-Style cast can be replaced by reinterpret_cast<> but when porting code you want to see if you could have used static_cast<>. 在最坏的情况下,C-Style强制转换可以被reinterpret_cast <>替换,但在移植代码时,您想要查看是否可以使用static_cast <>。 The point of the C++ casts is to make them stand out so you can see them and at a glance spot the difference between the dangerous casts the benign casts. C ++演员阵容的目的是让他们脱颖而出,这样你就可以看到他们,并且一眼就看出了良性演员的危险演员之间的区别。

一个典型的例子是尝试修改一个const字符串文字,它可能存在于受保护的数据段中。

出于优化原因,编译器可以将const数据放在存储器的只读部分中,并且尝试修改该数据将导致UB。

Static and const data are often stored in another part of you program than local variables. 静态和常量数据通常存储在程序的另一部分而不是局部变量中。 For const variables, these areas are often in read-only mode to enforce the constness of the variables. 对于const变量,这些区域通常处于只读模式以强制执行变量的常量。 Attempting to write in a read-only memory results in an "undefined behavior" because the reaction depends on your operating system. 尝试写入只读内存会导致“未定义的行为”,因为反应取决于您的操作系统。 "Undefined beheavior" means that the language doesn't specify how this case is to be handled. “未定义的beheavior”表示该语言未指定如何处理此案例。

If you want a more detailed explanation about memory, I suggest you read this . 如果你想要更详细的内存解释,我建议你阅读这个 It's an explanation based on UNIX but similar mecanism are used on all OS. 这是基于UNIX的解释,但在所有操作系统上都使用了类似的机制。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM