临时数组指针与局部/全局作用域:是否存在未定义行为?
测试代码
#include <iostream> #include <cstdint> #include <cstring> struct bt_data { uint8_t type; uint8_t data_len; const uint8_t* data; }; static const char* devName="LSM6DSR_Sensor"; bt_data ad[] = { { .type = (0x01), .data_len = (sizeof((uint8_t []) { (0x02 | 0x04) })), .data = (const uint8_t *)(((uint8_t []) { (0x02 | 0x04) })), }, { .type = (0x09), .data_len = (strlen(devName)-1), .data = (const uint8_t *)(devName), }, }; int main() { uint8_t d1[] { (0x02 | 0x04) }; bt_data ad3[] = { { .type = (0x01), .data_len = 1, .data = d1, }, { .type = (0x09), .data_len = 14, .data = (const uint8_t *)devName, }, }; bt_data ad2[] = { { .type = (0x01), .data_len = (sizeof((uint8_t []) { (0x02 | 0x04) })), .data = (const uint8_t *)(((uint8_t []) { (0x02 | 0x04) })), }, { .type = (0x09), .data_len = (strlen(devName)-1), .data = (const uint8_t *)(devName), }, }; std::cout << "global: " << sizeof(ad) / sizeof(ad[0]) << " " << ad[1].data << std::endl; std::cout << "local, extra: " << sizeof(ad3) / sizeof(ad3[0]) << " " << ad3[1].data << std::endl; std::cout << "local temp: " << sizeof(ad2) / sizeof(ad2[0]) << " " << ad2[1].data << std::endl; }
不同编译器输出
GCC 12.2.0 输出
Program returned: 0
global: 2 LSM6DSR_Sensor
local, extra: 2 LSM6DSR_Sensor
local temp: 1
GCC 14 输出
Program returned: 0
global: 2 LSM6DSR_Sensor
local, extra: 2 LSM6DSR_Sensor
local temp: 2 LSM6DSR_Sensor
Clang 提示
Clang编译器会对ad2发出警告,提示临时数组在表达式结束时销毁,导致后续指针访问悬空。
技术问题解答
1. 为什么全局作用域中的临时数组不会被销毁?
全局作用域的对象属于静态存储期,其生命周期从程序启动时开始,直到程序终止才结束。初始化全局数组ad时用到的临时复合数组,会被编译器分配到静态存储区域(如.rodata或.data段),不会在初始化表达式结束后被回收,因此ad中保存的指针始终指向有效内存。
而局部作用域(如main里的ad2)中的临时数组属于自动存储期,生命周期仅局限于创建它的完整表达式范围内——ad2初始化完成后,这些临时数组就会被销毁,此时ad2中的指针变为悬空指针,后续访问属于未定义行为。GCC 12.2.0中ad2元素数量显示为1,就是未定义行为导致的异常结果;GCC 14的输出看似正常,但这是编译器的非标准优化,依然不符合C++规范。
2. 该行为是否依赖特定C++标准?在C语言中(包括局部数组)情况是否有所不同?
C++标准层面:
C标准明确规定,自动存储期的临时对象(包括临时数组)的生命周期通常在创建它的完整表达式结束时终止。静态存储期的临时对象(全局初始化时使用的)则与程序生命周期一致。C17之前,聚合初始化中临时对象的生命周期规则存在模糊地带,这也是GCC不同版本输出差异的原因。但无论哪个C++版本,局部作用域中用临时数组初始化指针的行为都是不安全的,属于未定义行为。C语言中的差异:
C99及以后支持复合字面量,但静态存储期对象的初始化器必须是常量表达式。静态区的复合字面量会被分配到静态存储区域,生命周期与程序一致;局部作用域的复合字面量属于自动存储期,生命周期在所在代码块结束时终止。和C类似,用局部复合字面量的指针初始化局部结构体数组时,同样会产生悬空指针问题,但C标准对临时对象的生命周期规则更简单直接,没有C中复杂的延长规则。
内容的提问来源于stack exchange,提问作者Maximilian Matthä

