GCC strict aliasing

本文探讨了GCC编译器的Strict Aliasing规则如何影响代码优化,并通过具体示例展示了当代码违反这一规则时可能出现的问题及解决办法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

http://www.dutor.net/index.php/2012/07/gcc-strict-aliasing/

 事情是这样的。我们对tair(淘宝的分布式Key/Value系统)动了一次大手术,更换了网络框架,经过长时间的测试/调试,终于完全通过了回归测试。但要打包发布的时候,却发现服务器可以正常启动,但却完全无法接受请求。调试无果,对比打包前后程序的差异,仅在于是否使用-O2选项对程序进行编译优化。
  无头苍蝇一样,Google搜索“gcc optimization problems”,找到StackOverflow上面的这个帖子,“抱着试试看的心态”,在编译选项中加入-fno-strict-aliasing,bingo!
  -fno-strict-aliasing这个选项是做什么的?aliasing又是什么?C和C++的标准给出了说明:

Strict aliasing is an assumption, made by the C (or C++) compiler, that dereferencing pointers to 
objects of different types will never refer to the same memory location (i.e. alias eachother.)

  即是说,在strict aliasing规则下,C/C++编译器认为,“不同类型”的指针(准确说是lvalue)一定不会引用同一个内存区域(即aliasing)。在这个规则的前提下,编译器就可以进行相应的优化。看下面这个函数:

1
2
3
4
5
6
7
8
9
10
int n;
int foo(int *ptr) {
  n=1;
  *ptr=3;
  return n;
}
int main() {
  fprintf(stdout, "%d\n", foo(&n));
  return 0;
}

编译并运行:

1
2
3
4
$ cc main.c && ./a.out
3
$ cc main.c -O2 && ./a.out
3

  一切正常,不是吗?但如果把函数foo的参数类型改作double*,运行结果“可能”会是:

1
2
3
4
5
6
$ cc main.c && ./a.out
3
$ cc main.c -O2 && ./a.out
1
$ cc main.c -O2 -fno-strict-aliasing && ./a.out
3

  在加-O2选项的情况下程序编译该程序,输出竟然是1,难道*ptr=3没有被执行吗?不是的,*ptr=3确实是执行了的,全局变量n在函数返回时也确实已经是3了(你可以在fprintf之后打印出n值做验证),但是foo函数中的语句return n却被优化成了return 1。为什么呢?因为后者比前者稍了一次内存访问。编译器为什么做这样的优化,为什么在ptr为int*时不做此优化?
  这就涉及到strict aliasing的具体规则了。首先定义一下alias:两个不同的变量引用了同一个对象(内存区域),那么就称这两个变量互为alias。下面是C99中可以互为alias的所有情况,除此之外的其他情况下,如果编译时指定-fstrict-aliasing(-O2及以上优化时自动指定),那么就执行strict aliasing:

  • a type compatible with the effective type of the object,
  • a qualified version of a type compatible with the effective type of the object,
  • a type that is the signed or unsigned type corresponding to the effective type of the object,
  • a type that is the signed or unsigned type corresponding to a qualified version of the effective type of the object,
  • an aggregate or union type that includes one of the aforementioned types among its members (including, recursively, a member of a subaggregate or contained union),
  • a character type.

  大致是这样的:两个类型兼容的变量可以互为alias,即使使用了signed/unsigned和const/volatile等修饰符;一个类型可以与另一个包含与该类型兼容的成员的struct/union类型的变量互为alias;char类型可以与任意类型互为alias。C++中可以互为alias的还可以是父类与子类。

  可以使用-fno-strict-aliasing来取消strict aliasing规则,同时也就放弃了这个规则带来的优化空间,放弃了一定的性能提升。如果你也遇到了文章开头我遇到的问题,而且担心-fno-strict-aliasing的性能损失,那就只能找出违反规则的代码,调整该代码,或者仅仅取消该代码的strict aliasing。

  基本就是这样了,最后总结一下。GCC的aliasing与优化紧密相关,在指定-O2及以上优化级别时自动打开-fstrict-aliasing,执行strict aliasing规则以优化编译程序。如果你的程序不遵守该规则(比如上面foo函数中出现double*ptr和n同时应用n的情况),就极有可能受到惩罚。GCC中与strict aliasing相关的选项除了-fstrict-aliasing/-fno-strict-aliasing,还有warning选项-Wstrict-aliasing=n,这个选项在你违反stict aliasing时给出警告,其中n为检查的力度,一般设为2。
  
  最后,如果想深入了解strict aliasing,可以参考这篇Understanding Strict Aliasing。另外,GCC的官方文档中有和优化选项相关的描述,其中也提到了strict aliasing。


<think>好的,我现在需要帮助用户理解GCC编译器中的-g选项的作用和用法。首先,我得回忆一下自己之前学过的关于GCC的知识,特别是调试相关的选项。记得-g选项是用来生成调试信息的,这样在GDB中就可以调试程序了。但具体细节可能还需要再确认一下。 用户的问题很明确,就是想知道-g选项的作用及用法。根据系统级指令,我需要引用用户提供的参考资料中的相关内容。比如引用[1]提到,使用-g选项可以让生成的可执行文件被GDB调试,而如果没有这个选项,可能无法进行调试。这点很重要,应该放在回答的前面部分。 然后,关于用法,用户可能需要知道如何在编译命令中加入-g选项。例如,引用[1]中的例子是gcc -g,但具体怎么用呢?比如编译一个源文件时,是否需要同时使用其他选项?比如gcc -g -o program program.c这样的命令结构。同时,引用[2]和[4]提到了其他选项如-c和-I,但用户的问题集中在-g,所以可能需要忽略这些部分,除非有相关的内容需要对比。 另外,用户提供的引用[3]是关于-fno-strict-aliasing的,这和调试无关,可能暂时不需要考虑。但需要确保回答中不包含无关的信息,只专注于-g选项。 接下来,我需要组织回答的结构。首先解释作用,再讲用法,最后可能给出示例。同时,根据用户的要求,生成相关问题。例如,用户可能还想知道如何结合优化选项使用-g,或者如何检查调试信息是否生成,这些可能成为相关问题。 需要注意的是,回答要使用中文,并且数学公式用$...$,独立公式用$$...$$。不过在这个问题中可能用不到数学公式,但需要确保LaTeX语法正确。例如,如果有提及变量或路径,可能需要用行内代码格式,而不是数学公式。 另外,用户提供的示例中的Python代码块,可能需要类比到GCC的命令行示例,用代码块展示用法。例如: ```bash gcc -g -o my_program my_program.c ``` 这样用户能清楚看到如何将-g选项加入编译命令中。 还要注意引用标记的正确添加,比如在提到调试信息时引用[^1],在提到编译步骤时引用相关的例子。但需要确保引用与内容对应,例如引用[1]确实讨论了-g选项和GDB调试的关系。 最后,生成的相关问题应该围绕-g选项的延伸,比如调试信息的级别,与其他优化选项的兼容性,如何查看生成的调试信息等。这些问题可以帮助用户进一步探索该选项的使用。 总结步骤: 1. 解释-g选项的作用:生成调试信息,供GDB使用。 2. 说明用法:在编译命令中加入-g,可能与其他选项组合。 3. 示例代码:展示带-g选项的编译命令。 4. 注意事项:如不影响程序执行,文件大小影响,优化选项结合使用。 5. 相关问题:生成三个相关问题,帮助用户深入理解。 现在检查是否有遗漏的信息,比如引用[1]中提到的“仅使用gcc命令编译生成的可执行文件,无法借助GDB调试”,所以必须强调-g选项的必要性。同时,可能提到调试信息包括变量、行号等信息,这样程序崩溃时可以追踪到具体位置。 另外,是否需要提到不同级别的调试信息?比如-g1, -g3等,但用户的问题可能不需要这么深入,除非引用中有提到。但用户提供的引用中没有提到级别,所以可能暂时不涉及,除非自己记得相关知识,但根据指令,应主要参考提供的引用内容。 确认回答中的引用正确,比如在说明作用时引用[^1],在提到编译步骤时如果有相关引用则添加,否则可能不需要。例如,示例中的命令可能参考引用[4]中的-I选项的结构,但用户的问题是关于-g,所以引用可能集中在[^1]和可能的其他部分。 最后,确保回答结构清晰,分点说明,并使用代码块展示示例命令。</think>### GCC编译器 -g选项的作用及用法详解 #### 一、-g选项的核心作用 1. **生成调试信息** -g选项会在编译过程中向可执行文件中嵌入调试信息,包括**变量名、函数名、源代码行号、数据类型**等。 - 调试信息使得工具(如GDB)能定位程序崩溃位置、查看变量值、设置断点等[^1]。 2. **必要性说明** 若编译时未添加-g选项,生成的可执行文件将**无法通过GDB进行源码级调试**,只能分析汇编指令[^1]。 #### 二、基础用法与示例 1. **基本编译命令** 在`gcc`或`g++`命令中直接添加`-g`选项: ```bash gcc -g main.c -o my_program ``` - 调试信息会完整保留在可执行文件中。 2. **分步编译场景** 若需分步操作(预处理、编译、汇编、链接),需在每个阶段添加`-g`: ```bash gcc -g -c file1.c -o file1.o # 编译为.o文件时保留调试信息 gcc -g file1.o file2.o -o program # 链接时仍需携带-g ``` 3. **与其他选项的组合** - 与优化选项(如-O2)联合使用: ```bash gcc -g -O2 main.c -o optimized_program ``` - 调试信息可能因优化被部分简化,但仍是常用调试手段。 #### 三、注意事项 1. **对程序性能的影响** - 调试信息会**增大可执行文件体积**(可能增加20%-50%),但**不影响运行时性能**。 - 调试信息仅在调试时加载,不影响正常执行逻辑。 2. **调试信息验证** 通过`file`命令或`objdump`检查是否包含调试符号: ```bash file my_program # 输出中应包含"with debug_info" objdump --syms my_program | grep debug # 查看调试符号表 ``` #### 四、扩展应用场景 1. **核心转储分析** 程序崩溃时,结合`ulimit -c unlimited`生成核心转储文件,通过`gdb my_program core`可追溯崩溃位置。 2. **动态链接库调试** 编译共享库时添加`-g`,便于追踪库内部问题: ```bash gcc -g -shared -fPIC lib.c -o libmylib.so ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值