const_cast 未定义行为的重要示例

Non-trivial example of undefined behavior with const_cast

据我所知,以下代码是根据 c++ 标准(特别是第 7.1.5.1.4 [dcl.type.cv]/4 节)的未定义行为。

#include <iostream>

struct F;
F* g;

struct F {
    F() : val(5)
    {
        g = this;
    }
    int val;
};


const F f;

int main() {
    g->val = 8;
    std::cout << f.val << std::endl;
}

但是,对于我尝试过的每个编译器和优化设置,这都会打印“8”。

问题: 有没有一个例子可以用这种类型的 "implicit const_cast" 表现出意想不到的结果?

我希望能像

的结果一样壮观
#include <iostream>
int main() {
    for (int i = 0; i <=4; ++i)
        std::cout << i * 1000000000 << std::endl;
}

开启,例如,带有 -O2 的 gcc 4.8.5

编辑:标准中的相关部分

7.1.5.1.4: Except that any class member declared mutable (7.1.1) can be modified, any attempt to modify a const object during its lifetime (3.8) results in undefined behavior.

回复建议重复的评论;它不是重复的,因为我要的是一个出现 "unexpected" 结果的示例。

没那么壮观:

f.h(守卫省略):

struct F;
extern F* g;

struct F {
    F() : val(5)
    {
        g = this;
    }
    int val;
};

extern const F f;
void h();

TU1:

#include "f.h"
// definitions
F* g;
const F f;
void h() {}    

TU2:

#include "f.h"
#include <iostream>
int main() {
    h(); // ensure that globals have been initialized
    int val = f.val;
    g->val = 8;
    std::cout << (f.val == val) << '\n';
}

使用 g++ -O2 编译时打印 1,使用 -O0 编译时打印 0

"undefined" 行为的主要情况通常是,如果有人看到 const,他们会认为它不会改变。因此,const_cast 故意做了很多图书馆和程序不希望做的事情,或者认为是明确的未定义行为。请务必记住,并非所有 未定义的行为 都来自标准,即使这是该术语的典型用法。

就是说,我能够在标准库中找到一个地方,在那里可以应用这种思想来做一些我认为更狭义地被认为是未定义行为的事情:生成一个 std::map 和 "duplicate keys":

#include "iostream"
#include "map"

int main( )
{
    std::map< int, int > aMap;

    aMap[ 10 ] = 1;
    aMap[ 20 ] = 2;

    *const_cast< int* >( &aMap.find( 10 )->first ) = 20;

    std::cout << "Iteration:" << std::endl;
    for( std::map< int,int >::iterator i = aMap.begin(); i != aMap.end(); ++i )
        std::cout << i->first << " : " << i->second << std::endl;

    std::cout << std::endl << "Subscript Access:" << std::endl;
    std::cout << "aMap[ 10 ]" << " : " << aMap[ 10 ] << std::endl;
    std::cout << "aMap[ 20 ]" << " : " << aMap[ 20 ] << std::endl;

    std::cout << std::endl << "Iteration:" << std::endl;
    for( std::map< int,int >::iterator i = aMap.begin(); i != aMap.end(); ++i )
        std::cout << i->first << " : " << i->second << std::endl;
}

输出为:

Iteration:
20 : 1
20 : 2

Subscript Access:
aMap[ 10 ] : 0
aMap[ 20 ] : 1

Iteration:
10 : 0
20 : 1
20 : 2

内置 g++.exe (Rev5, Built by MSYS2 project) 5.3.0

显然,存储对中的访问密钥和密钥值不匹配。 20:2 对似乎也无法访问,除非通过迭代。

我猜这是因为 map 是作为树实现的。更改值会保留它最初所在的位置(10 会去的地方),因此它不会覆盖另一个 20 键。同时,添加实际的 10 不会覆盖旧的 10 因为在检查键值时,它实际上并不相同

我现在没有可参考的标准,但我认为这在某些层面上违反了 map 的定义。

它也可能导致更糟糕的行为,但使用我的 compiler/OS 组合我无法让它做任何更极端的事情,比如崩溃。