在我们的代码库中,我们的代码可以归结为以下内容(尝试使用动态堆栈分配而不是堆分配):
#include <memory>
#include <alloca.h>
void f(long long const* data, size_t len);
void h(int const* data, size_t len)
{
std::unique_ptr<char[]> heapBuff;
auto nbytes = len * sizeof(long long);
long long* arr;
if (nbytes <= 1024) {
arr = reinterpret_cast<long long*>(alloca(nbytes));
}
else {
heapBuff.reset(new char[nbytes]);
arr = reinterpret_cast<long long*>(heapBuff.get());
}
for (size_t i=0; i < len; ++i)
arr[i]=data[i];
f(arr, len);
}
(编译器资源管理器中的完整示例),从版本 11 开始,GCC中的编译结果会出现“可能使用未初始化”警告。
/app/h.cpp: In function 'void h(const int*, size_t)':
/app/h.cpp:23:4: warning: 'arr' may be used uninitialized [-Wmaybe-uninitialized]
23 | f(arr, len);
| ~^~~~~~~~~~
In file included from /app/h.cpp:2:
/app/f.hpp:4:6: note: by argument 1 of type 'const long long int*' to 'void f(const long long int*, size_t)' declared here
4 | void f(long long const* data, size_t len);
| ^
Clang 或更早版本的 GCC 不会产生此警告。
在我看来,没有导致arr
指针未初始化的路径,并且代码非常简单,编译器应该可以轻松找出原因。我是不是漏掉了什么?
有一点很奇怪,用 替换alloca()
不会malloc()
影响警告,但用一些非内联void* allocate(size_t)
函数替换 会消除警告。
首先,我发现代码中有六个小问题:
从技术上讲,
new char[nbytes]
无法隐式创建嵌套在数组long long
中的对象数组char
。因此,通过的结果进行访问具有未定义的行为。这可以通过替换为或reinterpret_cast<long long*>
来轻松解决。在这两种情况下,启动该类型数组的生命周期也会隐式创建嵌套在数组中的对象(隐式生命周期类型)。char
unsigned char
std::byte
std::unique_ptr::get
将返回指向第一个数组元素的指针,而不是指向任何嵌套long long
对象的指针(假设任何对象都是隐式创建的)。因此,您需要std::launder
在之后进行额外的调用reinterpret_cast
。(目前有一项提议是更改标准,以便launder
不再需要此调用。)目前尚不清楚它是否
alloca
也隐式创建了对象。由于这是一个非标准函数,标准显然对此没有任何说明,我怀疑任何文档都会对此做出判断。然而,在实践中,对我来说唯一有意义的行为是它确实隐式创建了对象。据推测它还会返回指向这种隐式创建的对象的指针。len * sizeof(long long)
当精确结果大于 的最大可表示值时, 可能会回绕size_t
。在这种情况下,在循环中,arr[i]=data[i]
最终将访问arr
越界,从而导致未定义的行为。由于
alloca
不是标准化的,因此它不会返回与 适当对齐的内存long long
。GCC文档说__builtin_alloca
内存将与 对齐__BIGGEST_ALIGNMENT__
,这应该足够大以存储所有非过度对齐的类型。同样,由于
alloca
没有标准化,因此如果大小参数为,则返回什么并不明显0
。不幸的是,GCC 文档__builtin_alloca
没有说明。从我的测试来看,似乎返回了堆栈指针,尽管尝试从函数返回指针时出现了一些不寻常的行为。但是,我看不出它与 GCC 发出的警告有任何关联。即使将示例简化为
GCC 仍然抱怨(https://godbolt.org/z/vhnr3EaMr)。
有趣的是,如果你将任何东西存储到内存中,那么 GCC 就会停止抱怨(https://godbolt.org/z/x31Gacdrb)。
所以我怀疑 GCC 并不满意
len == 0
无法向分配的(零大小)内存写入任何内容的情况。对此的一个迹象是,添加早期返回len == 0
可以消除警告。我认为警告检查是否有内容写入内存的原因是您将它作为
const
指针传递,这表明该函数f
将(仅)从内存中读取。如果没有内容写入其中,则无法从中读取任何内容。如果将参数设为非-,则const
警告也会消失。