C语言中有哪些有用的GCC标志?

178

除了设置-Wall和设置-std=XXX之外,在C中还有哪些真正有用但不太知名的编译器标志可供使用?

我特别感兴趣的是任何其他的警告,以及在某些情况下将警告转换为错误,以尽可能地减少任何意外类型不匹配。


13
好的,“-save-temps”、“-Wshadow”和“-fmudflap”是我之前不知道的最好的发现,感谢所有人。 (译注:这段话已经是中文了,因此无需翻译) - Matt Joiner
据我所知的上下文:运行gcc -c [放置标志] -o myprog.o myprog.c以编译(而非链接)C程序。 - Rory O'Kane
相关:*C语言推荐的GCC警告选项* - Peter Mortensen
24个回答

159
这是我的建议:
  • -Wextra-Wall: 必需的。
  • -Wfloat-equal: 通常测试浮点数相等性是不好的,因此这个选项很有用。
  • -Wundef: 如果一个未初始化的标识符在 #if 指令中被评估,警告。
  • -Wshadow: 当局部变量与另一个局部变量、参数或全局变量重名时,或者当一个内置函数被重名时,警告。
  • -Wpointer-arith: 如果任何东西依赖于函数或 void 的大小,则警告。
  • -Wcast-align: 当一个指针被转换为使目标所需的对齐方式增加时,警告。例如,在只能按两个或四个字节边界访问整数的机器上,如果将 char * 转换为 int *,则发出警告。
  • -Wstrict-prototypes: 如果函数没有指定参数类型,则警告其声明或定义。
  • -Wstrict-overflow=5: 当编译器基于有符号溢出不会发生的假设进行优化时,警告该情况。 (值5可能太严格,请参阅手册页面。)
  • -Wwrite-strings: 把字符串常量的类型设置为const char[length],这样将其地址复制到非const char *指针中会得到警告。
  • -Waggregate-return: 如果定义或调用了返回结构体或联合体的函数,则发出警告。
  • -Wcast-qual: 每当一个指针被转换以从目标类型中删除类型限定符时,发出警告*
  • -Wswitch-default: 每当switch语句没有default情况时,发出警告*
  • -Wswitch-enum: 每当一个switch语句具有枚举类型的索引并缺少该枚举的一个或多个命名代码的case时,发出警告*
  • -Wconversion: 警告可能改变值的隐式转换*
  • -Wunreachable-code:如果编译器检测到代码永远不会被执行,则发出警告*
  • 那些带有*标记有时会给出太多的误报警告,因此我只在需要时使用它们。


    17
    非常完整的列表,我只想再添加一个:-Wformat=2:在printf/scanf函数上进行额外的格式检查。 - schot
    2
    这些不是都被-Wall隐含了吗? - chacham15
    3
    @chacham15,不,我不这么认为。http://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html - Alok Singhal
    1
    @Alok 嗯,也许这在发行版中不是标准的?我知道在我的 mbp 上,我必须明确关闭 -Wwrite-strings,因为我非常讨厌它。 - chacham15
    @chacham15,也许是这样。但是-Wwrite-strings的描述明确说明它不是-Wall的一部分:http://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html#index-Wwrite_002dstrings-415。也许你的设置中有其他东西设置了该标志?或者你正在编译C++? - Alok Singhal
    2
    “-Wunreachable-code” 的功能已被移除(然而,如果仍在使用该选项,gcc 不会发出警告...)https://gcc.gnu.org/ml/gcc-help/2011-05/msg00360.html - René Nyffenegger

    71

    -f 代码生成选项中的几个选项比较有趣:

    • -fverbose-asm 在使用 -S 编译成汇编输出时非常有用,它会添加一些注释来增加信息量。

    • -finstrument-functions 在每个函数的进入和退出点处添加调用用户提供的分析函数的代码。

    • --coverage 对程序中的分支和调用进行插装,并创建一个覆盖率文件,在运行程序时产生覆盖数据,可以通过gcov程序格式化,以帮助分析测试覆盖率。

    • -fsanitize={address,thread,undefined} 分别启用 AddressSanitizer、ThreadSanitizer 和 UndefinedBehaviorSanitizer 代码检查器,这些程序会在运行时对程序进行各种类型的错误检查。

    此前的答案还提到了-ftrapv,但这个功能已经被-fsanitize=signed-integer-overflow取代,后者是通过-fsanitize=undefined启用的一种sanitizer之一。


    1
    对于-ftrapv,请参考这里https://dev59.com/3XrZa4cB1Zd3GeqP6sUj ..看起来有一个长期等待修复的bug。 - Arjun Sreedharan
    你能检查一下上面的评论吗? - Suraj Jain
    2
    -ftrapv 实质上已被 -fsanitize=signed-integer-overflow 取代。 - Marc Glisse

    56

    始终使用 -O 或更高优化级别 (-O1, -O2, -Os, 等等)。在默认的优化级别下,gcc 会选择编译速度而不做足够的分析来警告未初始化的变量等问题。

    考虑采用 -Werror 策略,因为没有停止编译的警告往往会被忽视。

    -Wall 基本上开启了很可能是错误的警告。

    -Wextra 包含的警告倾向于标记常见的、合法的代码。它们可能对代码审查很有用(尽管类似 lint 程序发现了更多的陷阱并且更加灵活),但我不会在正常开发中打开它们。

    如果项目开发人员对浮点数不熟悉,则使用 -Wfloat-equal 是一个好主意,否则就不是。

    -Winit-self 是有用的;我想知道为什么它没有包括在 -Wuninitialized 中。

    如果您有大量可移植的代码无法与 -pedantic 兼容,则使用 -Wpointer-arith 很有用。


    41

    -save-temps

    此选项会将预处理和汇编的结果保存在文件中。

    保存预处理后的源码对于调试宏非常有用。

    保存汇编代码对于确定哪些优化生效非常有用。例如,您可能想要验证GCC是否对某些递归函数进行了尾调用优化,因为如果没有进行优化,您可能会造成堆栈溢出。


    我确实想知道你是如何做到的...如果需要,我一直只是要求gcc转储汇编代码。 - user257111

    41
    在我看来,最有用的标志是-g,它将调试信息放入可执行文件中,以便您可以在程序执行时调试并逐步查看源代码(除非您精通阅读汇编语言并喜欢使用stepi命令)。这对于编程非常有帮助。

    36

    -fmudflap选项会在所有风险指针操作中添加运行时检查,以捕获未定义行为。这有效地使您的程序免疫缓冲区溢出,并有助于捕获各种悬空指针。

    请参阅Mudflap指针调试

    这是一个演示:

    文件 mf.c

    int main()
    {
      int a[10];
      a[10] = 1; // <-- Oh noes, line 4
    }
    

    编译和执行

    gcc -fmudflap mf.c -lmudflap
    ./a.out
    

    输出:

    *******
    mudflap violation 1 (check/write): time=1280862302.170759 ptr=0x7fff96eb3d00 size=44
    pc=0x7f3a575503c1 location=`mf.c:4:2 (main)'
          /usr/lib/libmudflap.so.0(__mf_check+0x41) [0x7f3a575503c1]
          ./a.out(main+0x90) [0x400a54]
          /lib/libc.so.6(__libc_start_main+0xfd) [0x7f3a571e2c4d]
    Nearby object 1: checked region begins 0B into and ends 4B after
    mudflap object 0xf9c560: name=`mf.c:3:6 (main) a'
    bounds=[0x7fff96eb3d00,0x7fff96eb3d27] size=40 area=stack check=0r/3w liveness=3
    alloc time=1280862302.170749 pc=0x7f3a57550cb1
    number of nearby objects: 1
    

    嗯,mudflap看起来相当恶心 :P - Matt Joiner
    12
    自GCC 4.9版本起,不再支持-fmudflap选项,编译时会报出警告:warning: switch '-fmudflap' is no longer supported。现已被AddressSanitizer取代。 - Agostino
    是的,最后一个包含它的版本是4.8.5(发布于2015年6月23日)。GCC 4.9.0发布于2014年4月22日(请注意日期重叠)。 - Peter Mortensen

    27

    这并不是真正与C/C++相关,但无论如何它都很有用:

    @file
    

    将所有之前回答中提到的好标志(即您们已经指定的)放在一个“文件”中,并使用此上述标志一起使用该文件中的所有标志。

    例如:

    文件:compilerFlags

    -Wall

    -std=c99

    -Wextra

    然后进行编译:

    gcc yourSourceFile @compilerFlags
    

    17

    -march=native用于为所编译的平台(芯片)生成优化代码。


    2
    如果您正在编译非本地计算机的代码,无法确定目标处理器,可以使用mtune=xxx选项进行优化,而不使用指令集。例如,mtune=generic选项会针对“平均”情况的处理器进行优化更新。 - Turix

    16

    如果您需要了解编译器预定义的预处理器标志:

    echo | gcc -E -dM -


    15

    这个选项不会帮助检测错误,但很少被提到的-masm=intel选项可大大改善使用-S检查汇编输出的体验。

    AT&T汇编语法让我头疼不已。


    2
    在我看来,AT&T和Intel之间的区别就像C#和Java之间的区别一样。只是语法不同而已。都很糟糕。 :) - Matt Joiner
    2
    +1 @michael 因为让gcc使用英特尔语法而不是可怕的AT&T,我表示赞赏。检查汇编已经消耗了足够的脑力 - 没有必要浪费脑力去记住操作码中源操作数在目标操作数之前的规则。现在,如果gcc像其他编译器一样支持__asm {}内联就好了! - greatwolf

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接