对联合体非活跃成员执行指针算术运算是否属于未定义行为?
这是个相当精准的问题,正好戳中了C++标准里联合体与指针算术交互的灰色地带。咱们一步步拆解分析:
首先先看你给出的代码:
struct sso { union { struct { char* ptr; char size_r[8]; } large_str; char short_str[16]; }; const char* get_tag_ptr() const { return short_str+15; } };
先明确核心问题
你想确认:无论联合体的活跃成员是large_str还是short_str,short_str+15是否都不会触发未定义行为(UB)。
从C++标准条款入手分析
首先看你提到的[basic.expr](准确来说是[expr.add]条款中关于指针算术的规则):
指针与整数的加法结果仅在以下情况定义:指针指向数组对象的一个元素或数组最后一个元素之后的位置,且结果指向同一数组对象的一个元素或最后一个元素之后的位置。
现在的争议点在于:当联合体的活跃成员不是short_str时,short_str这个数组对象是否还能被视为“有效的数组对象”,从而让指针算术合法?
1. 联合体成员的存储空间与生命周期
根据[class.union],联合体的所有成员共享同一存储空间,且大小足以容纳最大的成员。这里large_str在64位系统下大小是sizeof(char*) + 8 = 16字节,和short_str完全一致,所以整个联合体的大小就是16字节。
对于short_str这种平凡类型(char[16]是平凡类型,满足平凡初始化、平凡析构等条件),根据[basic.life]的规则:
平凡类型对象的生命周期,从其符合对齐要求的存储空间被分配时就开始了——不需要显式完成初始化(因为平凡初始化是默认完成的)。
这意味着,只要sso对象的存储空间被分配,short_str作为平凡类型的数组,其生命周期就已经开始了,不管它是不是联合体的活跃成员。它是一个真实存在的数组对象,占据着联合体的整个存储空间。
2. short_str+15的合法性
short_str是char[16]类型的数组,short_str本身会隐式转换为指向第一个元素的指针。short_str+15指向的是数组的第15个元素(也就是最后一个元素),完全符合[expr.add]中“指向同一数组对象的元素”的要求,这显然是合法的指针算术操作。
退一步说,即使有人对“非活跃成员的对象生命周期”有不同解读,short_str+15指向的是联合体存储空间的第16个字节(从0计数),而C++标准明确允许通过char/unsigned char指针访问任何对象的存储空间([basic.lval]条款)。这个指针指向的是已分配存储空间内的一个位置,对于char类型指针来说,这种算术操作本身就是安全的——因为char的对齐要求最低,任何内存位置都符合其对齐需求。
结论
你的观点是完全正确的:short_str+15永远不会是未定义行为,无论联合体当前的活跃成员是哪一个。
内容的提问来源于stack exchange,提问作者Oliv

