【问题标题】:Uninitialized warning with GCC optimizerGCC 优化器的未初始化警告
【发布时间】:2011-10-22 06:21:34
【问题描述】:

我在 C 中实现了一棵树,现在我想定义一个包装树集。我的树在 tree.h 中有一个迭代器:

typedef struct tree_iter_t {
    void *current;
    tree_t *tree;
    unsigned char info : 2;
} tree_iter_t;

还有一个在tree.c中获取迭代器的函数:

tree_iter_t titerator(tree_t *t) {
    tree_iter_t it;
    it.current = t->min;
    if (t->min) it.info = 0;
    else it.info = 3;
    it.tree = t;
    return it;
}

我可以用-Wall -O2 编译它而没有警告。对于我的树集,我在 tset.h 中定义了我的树集迭代器,如下所示:

typedef struct tset_t tset_t;

typedef struct tset_iter_t {
    tree_iter_t iter;
} tset_iter_t;

以及在 tset.c 中获取它的函数。

struct tset_t {
    tree_t *tree;
};

tset_iter_t tsiterator(tset_t *ts) {
    tset_iter_t it;
    it.iter = titerator(ts->tree);
    return it;
}

当我使用gcc -Wall -c -combine tset.c tree.c 编译时,我没有问题,但是当我添加-O2 时,我在返回语句上收到警告:warning: ‘it.iter.tree’ is used uninitialized in this function。为什么 GCC 有这个问题?我错过了什么明显的东西吗?它看起来对我来说是初始化的。我跑了gcc -S -O2 tset.c 试图了解发生了什么,GCC 没有给出任何警告并产生了这个:

tsiterator:
pushl   %ebp
movl    %esp, %ebp
pushl   %ebx
subl    $36, %esp
movl    12(%ebp), %edx
movl    8(%ebp), %ebx
leal    -20(%ebp), %eax
movl    (%edx), %edx
movl    %eax, (%esp)
movl    %edx, 4(%esp)
call    titerator
movzbl  -12(%ebp), %edx
movzbl  8(%ebx), %eax
andl    $3, %edx
andl    $-4, %eax
orl     %edx, %eax
subl    $4, %esp
movb    %al, 8(%ebx)
movl    -16(%ebp), %eax
movl    %eax, 4(%ebx)
movl    -20(%ebp), %eax
movl    %eax, (%ebx)
movl    %ebx, %eax
movl    -4(%ebp), %ebx
leave
ret $4

我知道优化会生成一些看起来很奇怪的代码,但是这到底是怎么回事!?我所有的其他(优化的)包装函数都是 10 行汇编(只是调用树函数的常用函数调用开销)。 gcc -O2 -S -combine tset.c tree.c 给了我警告,内联滴定器,并产生了这个:

tsiterator:
pushl   %ebp
movl    %esp, %ebp
movl    12(%ebp), %edx
pushl   %ebx
movl    8(%ebp), %eax
movl    (%edx), %ecx
movl    4(%ecx), %edx
movl    %ecx, 4(%eax)
cmpl    $1, %edx
movl    %edx, (%eax)
movzbl  8(%eax), %edx
sbbl    %ebx, %ebx
andl    $3, %ebx
andl    $-4, %edx
orl     %ebx, %edx
movb    %dl, 8(%eax)
popl    %ebx
popl    %ebp
ret $4

当我将实现更改为:

tset_iter_t tsiterator(tset_t *ts) {
    tset_iter_t it;
    tree_iter_t i = titerator(ts->tree);
    it.iter = i;
    return it;
}

没有问题。在第一种情况下,GCC 优化(或分析)了什么,为什么它会给我一个警告?

谢谢。

【问题讨论】:

  • tree_iter_t it; 然后it.tree = t;it.tree 在哪里?你为什么不发布真正的代码。
  • nitpick - 通过声明位域“char info : 2;”不会节省任何内存在 tree_iter_t 中。编译器仍然必须为每个实例分配固定数量的字节,因此“info”无论如何都会成为一个完整的字节。
  • 另一个 nitpick,默认情况下使用 gcc char 进行签名,因此当您在此代码中分配 it.info=3 时,您实际上将值包装为 -1(我只是试了一下以确保)。如果您打算将其设为位域,则应明确将其设为无符号,否则 (it.info == 3) 之类的代码可能无法像您预期的那样工作。
  • @ryan_s - 我已经更改了字符。祈祷我不要再改变它。
  • 我喜欢您对《星球大战》的参考,但恐怕我必须告诉您,该标准只指定了int(有符号/无符号/wahtever)和_Bool 类型的位域。大多数编译器都会很乐意接受unsigned char info :2;,但它在技术上是不标准的。

标签: c gcc compiler-warnings compiler-optimization


【解决方案1】:

我认为警告是一个错误。你使用的是哪个 gcc?当我使用 gcc 4.0 和 4.2 编译(当然是单个文件)时,不会发生这种情况。

这里是优化汇编器的注释版本。我在这里看不到任何未分配的内容,这就是我认为警告不正确的原因。我已经猜到了树结构。

tsiterator:
    pushl   %ebp
    movl    %esp, %ebp
    movl    12(%ebp), %edx  ; edx has pointer to ts
    pushl   %ebx
    movl    8(%ebp), %eax   ; eax has pointer to retval
    movl    (%edx), %ecx    ; ecx has ts->tree (?)
    movl    4(%ecx), %edx   ; edx has ts->tree->min (?)
    movl    %ecx, 4(%eax)   ; store ts->tree into retval->iter->tree
    cmpl    $1, %edx    
    movl    %edx, (%eax)    ; store ts->tree->min into retval->iter->current
    ;; This and the cmpl instruction above is all
    ;; handling the bitmasking for retval->iter->info.
    ;; Probably would be more efficient to not use a
    ;; bit mask here, as the compiler could remove the
    ;; second "and" and "or" instructions.
    movzbl  8(%eax), %edx   ; get current value of retval->iter->info
    sbbl    %ebx, %ebx  ; ebx = 0 if unsigned edx < 1, else -1
    andl    $3, %ebx    ; mask off new value            
    andl    $-4, %edx   ; mask off old value   
    orl     %ebx, %edx  ; combine old | new    
    movb    %dl, 8(%eax)    ; store combined into retval->iter->info
    popl    %ebx
    popl    %ebp
    ret $4

编辑:请注意,编译器会小心地保留 tree_iter_t.info 中随机未初始化垃圾的高 6 位。

【讨论】:

    【解决方案2】:

    我不明白为什么 gcc 可能会抱怨那个特定的字段 tree,但他是对的,您从函数返回的部分结构没有初始化。事实上,您基本上应该始终只初始化变量的一个字段,然后使用0 自动初始化其他字段。在您的情况下,最简单的是使用 C99 的指定初始化程序:

    tree_iter_t it = { .tree = t };
    

    然后您也可以在之后跳过对该字段的分配。

    【讨论】:

    • “您从函数返回的结构部分未初始化” - 哪些部分?
    • @Nick,部分中的其他位对应于info,可能还有其他填充。
    猜你喜欢
    • 2021-03-10
    • 1970-01-01
    • 2016-03-03
    • 2023-03-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多