玩strict aliasing

本文通过一个简单的C语言代码示例,探讨了GCC编译器的不同优化级别如何影响程序的行为,特别是-fstrict-aliasing选项如何改变对内存访问的假设。文章详细分析了违反C99 aliasing规则可能导致的问题,并提供了规避这些问题的方法。

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

转自:瀚海星云 ANSIC版 作者:xhacker

一. 不说废话,看代码
zjs@xhacker:/tmp$ cat tt.c
#include <stdio.h>

int main()
{
     int a = 0x12345678;
     short *p = (short *)&a;
     short temp;
     temp = *p;
     *p = *(p+1);
     *(p+1) = temp;
     printf("%x\n", a);
}
zjs@xhacker:/tmp$ gcc   -O2   tt.c
zjs@xhacker:/tmp$ ./a.out
12345678
zjs@xhacker:/tmp$ gcc -v
Using built-in specs.
Target: i486-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Debian 4.3.1-2' --with-bugurl=file:///usr/share/doc/gcc-4.3/README.Bugs --enable-languages=c,c++,fortran,objc,obj-c++ --prefix=/usr --enable-shared --with-system-zlib --libexecdir=/usr/lib
--without-included-gettext --enable-threads=posix --enable-nls --with-gxx-include-dir=/usr/include/c++/4.3 --program-suffix=-4.3 --enable-clocale=gnu --enable-libstdcxx-debug --enable-objc-gc --enable-mpfr --enable-targets=all --enable-cld
--enable-checking=release --build=i486-linux-gnu --host=i486-linux-gnu --target=i486-linux-gnu
Thread model: posix
gcc version 4.3.1 (Debian 4.3.1-2)

上面的指令把环境啥的都揭示的很清楚。这段代码主要是交换一个整数的前一个字和后一
个字,结果很奇怪是不,竟然没交换:D,是不是我土了代码写错?好,再看

zjs@xhacker:/tmp$ gcc tt.c
zjs@xhacker:/tmp$ ./a.out
56781234

awesome!结果完全正确

二。分析
表面上立马看出结果不同的原因在于gcc编译时的参数不同,gcc -O2优化开启了很多
优化选项,可以到gnu网站查的,其中有一项就是-fstrict-aliasing,这开启了
aliasing规则
1.那么什么是aliasing规则呢?先看gcc对-fstrict-aliasing的解释
Allows the compiler to assume the strictest aliasing rules
applicable to the language being compiled. For C (and C++), this
activates optimizations based on the type of expressions. In
particular, an object of one type is assumed never to reside at the same
address as an object of a different type, unless the types are almost
the same. For example, an unsigned int can alias an int, but not a
void* or a double. A character type may alias any other type.

再看c99标准对aliasing的
7 An object shall have its stored value accessed only by an lvalue
    expression that has one of the following types: {footnote 73}

      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), or

      a character type.

{footnote 73} The intent of this list is to specify those circumstances
in which an object may or may not be aliased.
gcc的说明更直白点,更容易懂。再看代码
short *p = (short *)&a;该语句中p是指向short的指针,&a是指向int的指针,
这破坏了aliasing规则。

2.破坏aliasing规则为什么会出错呢?
这要从c99为啥提了这么个东东说起,在我理解中,不同类型的指针一定不会指向
同一个内存位置会有两个好处,1是减少可能的load和store;2是编译器可以对指
令做些调整优化。
出错就是因为开启了strict aliasing规则后,gcc认为*p不会指向&a所在的内存位置
所以*p的操作和最后打印a没关系,指令可以reorder。实际上汇编结果表明正是如此
gcc -O2 -S tt.c后tt.s部分内容:
     movl     $305419896, 4(%esp) //可以看出直接送数据,根本不管*p的操作的
     movl     $.LC0, (%esp)
     call     printf

3.c99的restrict关键字
这个就不多说了。它起的作用是表明其修饰的指针不会被其它指针aliased
扩展了strict aliasing
4.对代码作者的影响
对新写的代码,要确保不违反这个规则,那么确实需要让不同指针指向同一个
内存位置怎么办?解决方案参考文献中写得很好,可以看看。当然有不少情形
下不违反这个规则非常麻烦,比如os的内核等特殊地方,那么加入fno-strict
aliasing参数,大家可以发现bsd内核、linux内核编译参数都加了这个

对于已有的代码,违反的地方非常多,那么可以加gcc的-fno-strict-aliasing
参数。

三。问题发现过程
我有时会把linux内核的部分代码用在应用层,linux内核编译时是加了
-fno-strict-aliasing这个参数的(内核编译时加V=1可以看到)
可应用的代码不一定,猜到是和内核的编译参数有关,最后没办法了,对于
所有的参数看着谁像,就去google,最后找到的

四。启发
os内核的编译参数都是各个大牛呕心沥血精心挑选的,选这个参数有他们的用
意,体会这种用意的同时,我们的水平也会提升。这让我想起曾经一款处理器
平台,它的linux模块加载老是不成功,找来找去最后还是找到了编译参数。那
是我第一次和编译参数做斗争:),现在看来和编译参数斗其乐无穷

ps:代码实验必需确保你编译器支持c99标准,而同时有打开和关闭aliasing规则的
参数开关,gcc3.X以后的就可以,微软的不清楚


两个比较好的参考文献
1.http://en.wikipedia.org/wiki/Aliasing_(computing)
2.http://www.cellperformance.com/mike_acton/2006/06/understanding_strict_aliasing.html
<think>我们正在讨论GCC和Clang编译器选项中的`-fno-strict-aliasing`。该选项用于禁用严格别名规则(strictaliasingrules),允许通过不同类型的指针访问同一内存区域,而不会触发未定义行为(undefinedbehavior)。###严格别名规则简介严格别名规则是C/C++标准中的一项规则,它规定通过不同类型的指针访问同一内存区域(除某些例外情况,如`char*`)会导致未定义行为。编译器在开启严格别名优化(通常使用`-O2`或更高优化级别时默认启用)时会基于此规则进行优化,假设指向不同类型的指针不会指向同一内存地址。例如:```cinta=1;float*b=(float*)&a;//违反严格别名规则*b=2.0f;```这段代码在开启严格别名优化时可能导致不可预测的结果,因为编译器假设`int*`和`float*`不会指向同一内存位置。###`-fno-strict-aliasing`的作用禁用严格别名规则,允许使用不同类型的指针安全地别名化(alias)同一内存区域:1.**避免未定义行为**:某些特殊场景(如类型转换、内存池管理等)需要别名化时,使用该选项确保程序行为定义良好。2.**兼容旧代码**:遗留代码中可能使用了指针强制转换,禁用严格别名规则能维持代码的原有行为。3.**诊断工具**:开发过程中用于调试因严格别名导致的问题。###使用场景1.**网络协议处理**:直接通过指针类型转换解析数据包结构。2.**内存池/对象池**:对象复用机制中常用不同类型的指针转换管理内存块。3.**嵌入式系统**:直接操作硬件寄存器时,寄存器结构体指针转换访问。4.**跨语言交互**:与汇编或动态生成的代码交互时可能存在别名问题。###性能影响禁用严格别名规则可能使编译器无法执行某些内存访问优化:-每次通过指针访问内存时,编译器需要重新读取数据(因假设相同内存地址可能被不同指针修改)。-可能降低性能(尤其是在高频操作的代码中)。因此,除非必要(如确保正确性),通常不建议全局禁用严格别名规则。推荐做法:-在必须使用别名的局部代码段使用`__attribute__((__may_alias__))`。-将可能别名化的操作封装在独立编译的模块中,对该模块使用`-fno-strict-aliasing`。###总结`-fno-strict-aliasing`用于兼容需要跨指针类型访问内存的代码,解决因编译器优化引发的未定义行为问题。使用时需权衡性能与正确性。§§相关问题§§1.如何通过代码标记允许特定指针进行别名化(如使用`__attribute__((__may_alias__))`)?2.GCC中开启严格别名优化的选项是什么?(默认在`-O2`及以上级别开启)3.严格别名规则在C++中相比C语言有何差异?4.`-fstrict-aliasing`和`-Wstrict-aliasing`选项分别有什么作用?</think> ### `-fno-strict-aliasing` 编译器选项详解 #### 作用与目的 1. **禁用严格别名规则**: - 在 C/C++ 中,严格别名规则禁止通过不同类型的指针访问同一内存区域(例如通过 `int*` 和 `float*` 访问同一地址)。 - `-fno-strict-aliasing` 会禁用此规则,允许通过不同类型的指针读写同一内存位置。 - 例:`int a = 1; float* b = (float*)&a;` 不会引发未定义行为。 2. **避免潜在编译错误**: - 当代码存在指针类型转换时(常见于底层系统编程或硬件交互),此选项确保编译器不会因"假设指针不重叠"而错误优化代码。 - 典型案例:网络协议解析、内存池管理、序列化反序列化等场景。 #### 使用场景 1. **兼容遗留代码**: - 当维护旧代码库时,若代码包含不符合严格别名规则的指针转换,需启用此选项避免编译失败。例如: ```c struct Data { int id; char buf[128]; }; void process(void *ptr) { float *f = ptr; // 危险的类型转换 /*...*/ } ``` 2. **硬件寄存器访问**: - 在嵌入式开发中访问硬件寄存器时,常需要将地址强制转换为不同数据类型的指针: ```c #define REG ((volatile uint32_t*)0xFFFF0000) ``` 3. **高性能内存操作**: - 内存复制/填充等底层操作可能需要类型转换优化,如: ```cpp void fast_memset(void* dst, int val, size_t len) { uint64_t *d64 = dst; // 使用 64 位写提升性能 /*...*/ } ``` #### 注意事项 1. **潜在风险**: - 违反语言标准,可能导致未定义行为(UB)。仅在充分理解场景后使用。 - 可能掩盖代码逻辑错误(如无意中的内存覆盖)。 2. **性能权衡**: - 禁用后编译器无法优化基于类型的内存访问,可能轻微降低性能(通常在基准测试中差异 <3%)。 3. **推荐替代方案**: - 使用 `__attribute__((__may_alias__))`(GCC)或 `__unaligned`(Clang)标记特定指针。 - 使用 `memcpy` 进行类型安全的字节拷贝: ```c int a = 42; float b; memcpy(&b, &a, sizeof(int)); // 合法且安全的转换 ``` > **参考建议**:在构建配置中局部启用而非全局使用(如仅对特定文件启用)[^1]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值