RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 1365803
Accepted
Barracudach
Barracudach
Asked:2022-05-25 18:50:40 +0000 UTC2022-05-25 18:50:40 +0000 UTC 2022-05-25 18:50:40 +0000 UTC

为什么在表达式中寻址零内存不会引发异常?

  • 772

有一个结构:

struct S {
        char   m0;
        double m1;
        short  m2;
        char   m3;
    };

这是它的工作原理:

std::cout << &(((S*)0)->m1) << std::endl;

这就是它抛出异常的方式нарушение прав доступа...:

std::cout << (((S*)0)->m1) << std::endl;

为什么会这样?毕竟,在第一个子表达式中还可以访问未分配的内存。

c++
  • 3 3 个回答
  • 10 Views

3 个回答

  • Voted
  1. Harry
    2022-05-25T19:02:52Z2022-05-25T19:02:52Z

    实际上,在第二种情况下,您访问了内存(查看值但地址不正确),但在第一种情况下,您没有:计算字段的地址,即 它只是做简单的算术(考虑对齐),没有真正的内存访问。

    我不知道这种行为有多正确(如果从标准的角度来看有任何 UB),但实际上它只是offsetof对一个不会威胁任何东西的领域的计算。

    从 stddef.h 到 VC++:

    #if defined _MSC_VER && !defined _CRT_USE_BUILTIN_OFFSETOF
        #ifdef __cplusplus
            #define offsetof(s,m) ((::size_t)&reinterpret_cast<char const volatile&>((((s*)0)->m)))
        #else
            #define offsetof(s,m) ((size_t)&(((s*)0)->m))
        #endif
    #else
        #define offsetof(s,m) __builtin_offsetof(s,m)
    #endif
    

    我怀疑是否有一个编译器会在这种情况下实际执行取消引用...

    • 4
  2. Best Answer
    wololo
    2022-05-25T22:08:16Z2022-05-25T22:08:16Z

    求值时,将 form 的表达式E1->E2转换为等效的 form (*(E1)).E2。expr.ref / 2:

    表达式E1->E2转换为等价形式(*(E1)).E2;

    我们得到这个表达式被((S*)0)->m1解释为一个表达式( *((S*)0) ).m1。那些。这是对象类型空指针被取消引用的地方,这是未定义的行为。本声明基于语言标准的以下条款。

    expr.unary.op / 1:

    一元运算*符执行间接:应用它的表达式应该是指向对象类型的指针,或指向函数类型的指针,结果是对表达式指向lvalue的对象或函数的引用。

    取消引用指针会产生一个引用对象的左值,但空指针不指向任何对象。

    dcl.ref/注2:

    特别是,在定义良好的程序中不能存在空引用,因为创建此类引用的唯一方法是将其绑定到通过空指针间接获得的“对象”,这会导致未定义的行为。

    它明确指出取消引用空对象指针会导致未定义的行为。

    enSO的相关问题:

    • 可以空引用吗?
    • C++ 标准:取消引用 NULL 指针以获取引用?
    • 取消引用 NULL 指针是否被视为未指定或未定义的行为?
    • 过去结束下标的明显规范不足:对于原始数组和 std::vector。已经果断解决了吗?

    因此,两个表达式的行为&(((S*)0)->m1)和(((S*)0)->m1)都是未定义的,因为 他们的计算需要取消引用空对象指针。并且编译器在优化过程中可以避免实际访问指针所指向的“对象”并不重要。

    语言标准不需要未定义的行为。程序可能会成功编译并表现出预期的行为,也可能会在运行时失败,或者可能会观察到其他一些行为。


    事实上,取消引用不指向对象的指针的情况要复杂一些。让我们看下面的例子:

    char a[10];
    //Т.к. a[10] эквивалентно *(a+10), то UB - разыменовали указатель на гипотетический элемент за последним элементом массива.
    char *b = &a[10];
    

    在这个例子和问题的例子之间

    std::cout << &(((S*)0)->m1) << std::endl;
    

    有一定的相似性——实际上,指针所指向的“对象”的值并不重要。

    在这里&a[10],我们不关心数组最后一个元素后面的值是什么——我们需要一个指向数组最后一个元素后面的元素的指针。同样,这里&(((S*)0)->m1)字段的具体值对我们来说并不重要m1,我们感兴趣的是指针。

    按照直观的期望重新定义上述代码片段的行为会很好。

    此外,语言标准存在一些不一致之处。运算符typeid声明定义了取消引用空指针的结果。expr.typeid / 3:

    当typeid应用于类型为多态类类型的泛左值时,结果引用std​::​type_­info表示泛泛值所引用的最派生对象(即动态类型)的类型的对象。如果通过将一元 ``* 运算符应用于指针57并且该指针是空指针值来获得泛左值,则该typeid表达式将引发与异常类型的处理程序匹配的类型的std​::​bad_­typeid异常。

    在实际上不需要访问该值的情况下,已经进行了几次尝试以使不指向对象的指针的取消引用合法化。

    在 C 中,运算符&和*在某些情况下会相互抵消(请参阅:数组指针微妙之处)。

    在 C++ 语言中,他们尝试引入一种特殊的左值 -空左值,但这些尝试仍停留在草稿阶段(请参阅:Isindirection through a null pointer undefined behavior?)。


    在一种情况下,语言标准需要对实现进行相当严格的检查,以检查表达式中是否存在未定义的行为——这些是常量表达式。

    考虑以下代码:

    #include <iostream>
    
    struct S
    {
        int m;
    };
    
    int main()
    {
        static S s;
        constexpr const int* p = &(((S*) &s )->m);
        std::cout << p;
    }
    

    此代码编译并成功运行(g++,clang)。

    但是,如果您尝试取消引用空指针(即使您实际上不需要访问该值),您也会收到编译错误。

    constexpr const int* p = &(((S*) 0 )->m);
    

    克++:

    error: dereferencing a null pointer in '*0' 
    constexpr const int* p = &(((S*) 0 )->m);
    

    铿锵声:

    error: constexpr variable 'p' must be initialized by a constant expression
    constexpr const int* p = &(((S*) 0 )->m);
    
    note: cannot access field of null pointer
    constexpr const int* p = &(((S*) 0 )->m);
    
    • 4
  3. user7860670
    2022-05-25T22:07:01Z2022-05-25T22:07:01Z

    与未定义行为的情况一样,这里的错误是期望一些特定的结果。甚至没有理由相信表单的构造((S*)0)->m1一定会导致程序中零地址的取消引用。这种取消引用存在于代码中,但它将在程序中变成什么是未知的。它可以被执行,也可以被跳过(因为编译器有权假设程序中没有空指针的解引用),或者它会破坏代码生成并导致完全意想不到的后果。

    • 3

相关问题

  • 编译器和模板处理

  • 指针。找到最小数量

  • C++,关于枚举类对象初始化的问题

  • 函数中的二维数组

  • 无法使用默认构造函数创建类对象

  • C++ 和循环依赖

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    表格填充不起作用

    • 2 个回答
  • Marko Smith

    提示 50/50,有两个,其中一个是正确的

    • 1 个回答
  • Marko Smith

    在 PyQt5 中停止进程

    • 1 个回答
  • Marko Smith

    我的脚本不工作

    • 1 个回答
  • Marko Smith

    在文本文件中写入和读取列表

    • 2 个回答
  • Marko Smith

    如何像屏幕截图中那样并排排列这些块?

    • 1 个回答
  • Marko Smith

    确定文本文件中每一行的字符数

    • 2 个回答
  • Marko Smith

    将接口对象传递给 JAVA 构造函数

    • 1 个回答
  • Marko Smith

    正确更新数据库中的数据

    • 1 个回答
  • Marko Smith

    Python解析不是css

    • 1 个回答
  • Martin Hope
    Alexandr_TT 2020年新年大赛! 2020-12-20 18:20:21 +0000 UTC
  • Martin Hope
    Alexandr_TT 圣诞树动画 2020-12-23 00:38:08 +0000 UTC
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5