我花了一天时间尝试调试 GCC 12.2 中的
maybe-uninitialized
警告。我终于将代码缩减为一个最小的可重现示例,表明该警告是误报。
下面的程序为四个“桶”分配内存,每个桶存储一个整数标志和一个字符指针,它们将通过指针算法访问。然后它将每个桶中的整数标志设置为
0
。然后它无限循环遍历每个桶并仅当标志为1
时才打印字符指针指向的(假设的)字符串。由于所有标志都设置为0
,因此不会打印任何内容。
#include <stdlib.h>
#include <stdalign.h>
#include <stdio.h>
#include <stddef.h>
// Diagram of layout of one bucket
// +---------+-----------------------------+---------+-----------------------------------+
// | int | padding to alignof( char* ) | char* | padding to alignof( max_align_t ) |
// +---------+-----------------------------+---------+-----------------------------------+
// Macros to describe layout
#define ROUND_UP( num, multiple ) ( ( ( num + multiple - 1 ) / multiple ) * multiple )
#define CHAR_PTR_OFFSET ROUND_UP( sizeof( int ), alignof( char* ) )
#define BUCKET_SIZE ROUND_UP( CHAR_PTR_OFFSET + sizeof( char* ), alignof( max_align_t ) )
int main( void )
{
// Allocate memory for four buckets
char *data = malloc( BUCKET_SIZE * 4 );
if( !data )
return 1;
// Set the int in each bucket to 0
for( size_t i = 0; i < 4; ++i )
*( int* )( data + BUCKET_SIZE * i ) = 0;
// Loop over all four buckets infinitely,
// dereferencing the char pointer in each one ONLY IF the integer is not zero (i.e. never)
for( size_t i = 0; ; i = ( i + 1 ) % 4 )
if( *( int* )( data + BUCKET_SIZE * i ) == 1 )
puts( *( char** )( data + BUCKET_SIZE * i + CHAR_PTR_OFFSET ) );
return 0;
}
GCC 与
-std=c11 -O3 -pedantic -Wall
生成以下警告:
31:7: warning: '*data_17 + _6' may be used uninitialized [-Wmaybe-uninitialized]
31 | puts( *( char** )( data + BUCKET_SIZE * i + CHAR_PTR_OFFSET ) );
如果我用
for( size_t i = 0; ; i = ( i + 1 ) % 4 )
替换for( size_t i = 0; i < 4; ++i )
,警告就会消失。似乎当循环是无限的(或者,在我的实际项目中,由于与 i
无关的某些条件而中止),GCC 无法检测到 *( int* )( data + BUCKET_SIZE * i ) == 1
将始终评估为 false,因此从未访问过 char 指针或取消引用。
我说 GCC 在这里产生误报是对的,还是我遗漏了什么?