中的\'offsetof\'宏是否会调用未定义的行为?

| 来自MSVC \的实现示例:
#define offsetof(s,m) \\
    (size_t)&reinterpret_cast<const volatile char&>((((s *)0)->m))
//                                                   ^^^^^^^^^^^
可以看出,它取消引用了空指针,该指针通常会调用未定义的行为。这是规则的例外还是正在发生的事情?     
已邀请:
        语言标准说“未定义行为”时,任何给定的编译器都可以定义行为。标准库中的实现代码通常依赖于此。因此,有两个问题: (1)代码UB是否符合C ++标准? 这是一个非常棘手的问题,因为众所周知,C ++ 98/03标准从未在规范性文本中明确指出(通常是UB取消引用空指针),这几乎是一种缺陷。
typeid
的例外表示它不是UB。 您可以肯定地说的是,将
offsetof
与非POD类型一起使用是UB。 (2)代码UB是否与为其编写的编译器有关? 不,当然不是。 给定编译器的编译器供应商代码可以使用该编译器的任何功能。 干杯,……     
        “未定义行为”的概念不适用于标准库的实现,无论它是宏,函数还是其他任何东西。 通常,不应将标准库视为用C ++(或C)语言实现。这也适用于标准头文件。标准库应符合其外部规范,但其他所有内容均是实现细节,不受该语言的所有其他要求限制。应始终将标准库视为以某种“内部”语言实现的,该语言可能与C ++或C非常相似,但仍不是C ++或C。 换句话说,您引用的宏不会产生未定义的行为,只要它特别是在标准库中定义的“ 2”宏即可。但是,如果您在代码中执行完全相同的操作(例如以相同的方式定义自己的宏),则确实会导致未定义的行为。 \“丽丝乔维酒庄,非丽丝博维酒”。     
        当C标准指定某些动作调用未定义行为时,这通常并不意味着禁止这些动作,而是实现可以自由地指定相应的行为,也可以随意指定其认为合适的行为。因此,当且仅当实现可以保证这些动作的行为与标准要求一致时,实现才可以在标准要求定义行为的情况下自由地执行此类动作。例如,考虑以下strcpy实现:
char *strcpy(char *dest, char const *src)
{
  ptrdiff_t diff = dest-src-1;
  int ch;
  while((ch = *src++) != 0)
    src[diff] = ch;
  return dest;
}
如果ѭ5和ѭ6是不相关的指针,则ѭ7的计算将产生未定义的行为。但是,在某些平台上,
char*
ptrdiff_t
之间的关系使得给定任何
char* p1, p2
,计算
p1 + (p2-p1);
将始终等于
p2
。在做出上述保证的平台上,ѭ13的上述实现将是合法的(并且在某些此类平台上可能比任何可行的替代方案都快)。但是,在某些其他平台上,此类功能可能总是会失败,除非两个字符串都属于同一分配对象。 相同的原理适用于
offsetof
宏。不需要编译器提供任何方式来获得等同于ѭ2behavior的行为(而不是通过实际使用该宏)。如果编译器的指针算术模型使得可以通过在对象上使用
->
运算符来获得所需的
offsetof
行为。空指针,那么它的
offsetof
宏可以做到这一点。如果编译器不支持在指向该类型实例的合法指针以外的任何地方使用
->
的工作,则它可能需要定义一个内部函数来计算字段偏移量并定义field2ѭ宏以使用该字段。重要的不是标准定义了使用标准库宏和函数执行的动作的行为,而是实现方式确保了此类宏和函数的行为符合要求。     
这基本上等同于询问这是否是UB:
s* p = 0;
volatile auto& r = p->m;
显然,不会对目标
r
生成任何内存访问,因为它是
volatile
,并且禁止编译器生成对
volatile
变量的虚假访问。但是“ 25”不是易失性的,因此编译器可能会生成对其的访问。根据标准,运算符的地址或转换为引用类型均不会创建未评估的上下文。 因此,我看不出有任何理由要求使用“ 23”,并且我同意其他人的看法,这是根据标准的未定义行为。当然,任何编译器都可以在标准留给实现者指定或未定义的地方定义行为。 最后,第ѭ27节中的注释说   特别是,空引用不能存在于定义良好的程序中,因为创建这种引用的唯一方法是将其绑定到通过解引用空指针而获得的“对象”,这会导致未定义的行为。     
        如果
m
在结构
s
中的偏移量为0,以及在某些其他情况下,这在C ++中不是未定义的行为。根据第232期(重点是我的):   一元*运算符执行间接操作:应用该表达式的表达式应为指向对象类型的指针或为函数类型的指针,并且结果为引用表达式所指向的对象或函数的左值(如果有) 。如果指针是空指针值(7.11 [conv.ptr])或指向数组对象的最后一个元素(8.7 [expr.add])的最后一个,则结果为空的左值,并且不引用任何对象或功能。空的左值不可修改。 因此,仅当
m
既不在偏移量0处,也不在对应于比数组对象的最后一个元素晚一个地址的偏移量时,才是未定义的行为。请注意,在C ++中允许向
null
添加0偏移,但在C中不允许。 正如其他人指出的那样,编译器被允许(并且极有可能)永远不会创建未定义的行为,并且可以与使用特定编译器的增强规范的库一起打包。     
        不,这不是不确定的行为。该表达式在运行时解析。 请注意,它是从空指针获取成员
m
的地址。它没有取消引用空指针。     

要回复问题请先登录注册