MySQL编译选项 -fno-strict-aliasing随手记

本文探讨了GCC编译器中的Strict Aliasing规则及其对性能的影响。通过对比不同编译选项下的代码行为,展示了Strict Aliasing如何影响类型转换,并提供了具体的示例代码和汇编输出,帮助理解其工作原理。

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

最近发布的MySQL8.0.2版本中,将gcc的编译选项从--fno-strict-aliasing移除,也就是说打开strict aliasing, 根据worklog #10344 的描述,在单线程的性能测试中,有最多%4的性能提升,还是相当可观的。这个flag在我们内部编译版本中也是一直打开的,但一直不知甚解。本文是网上搜索文档和自己试验的小结。

首先strict aliasing是个什么鬼? --fno-strict-aliasing对应的是--f-strict-aliasing,GCC文档的解释如下:

Allow 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.

Stackoverflow上关于strict aliasing规则的问题

当使用strict aliasing时, 编译器会认为在不同类型之间的转换不会发生,因此执行更激进的编译优化,例如reorder执行顺序。

Strcit aliasing只能隐式的开启或者显式的禁止掉。在-o2或更高的编译级别上会被隐式开启。

这里举个简单的例子,参考网络 上这篇文章

$cat x.c
#include <stdio.h>
#include <stdint.h>

int main()
{
        int a = 0x12345678;

        uint16_t* const sp = (uint16_t*)&a;
        uint16_t hi = sp[0];
        uint16_t lo = sp[1];

        sp[1] = hi;
        sp[0] = lo;

        printf("%x\n", a);

        return 0;
}

函数的功能很简单,对一个数字的高低位进行交换

gcc版本

$gcc --version
gcc (GCC) 4.4.6 20110731 (Red Hat 4.4.6-3)

执行strict aliasing (O2及以上默认打开)

$gcc -O2  x.c

$./a.out
12345678

非strict aliasing (显式指定-fno-strict-aliasing)

$gcc -O2 -fno-strict-aliasing x.c

$./a.out
56781234

不同的gcc flag,两次的执行结果居然完全不相同,只有第二次才实现了预期功能。因为默认情况下不报warning,我们把告警打开看看:

$gcc -O2 -Wstrict-aliasing x.c
x.c: In function ‘main’:
x.c:13: warning: dereferencing pointer ‘sp’ does break strict-aliasing rules
x.c:9: warning: dereferencing pointer ‘sp’ does break strict-aliasing rules
x.c:8: note: initialized from here
x.c:10: warning: dereferencing pointer ‘({anonymous})’ does break strict-aliasing rules
x.c:10: note: initialized from here
x.c:12: warning: dereferencing pointer ‘({anonymous})’ does break strict-aliasing rules
x.c:12: note: initialized from her

果然在使用strict aliasing时,因为破坏了严格aliasing的规则大量报警,因此如果我们要使用strict aliasing,一定要打开报警,并重视每个warning。

回到刚才的问题,为什么strict aliasing会输出最原始的数据,而不是修改后的数据呢 ? 看起来就好像后面的修改全部被忽略掉了一样。 我们来看看编译后的代码。可以看到两个汇编代码完全不相同。编译器认为代码里不可能出现不规范的类型转换,所以在错误的案例里,a的未被修改的值被直接抛给了printf函数

正确的 (gcc -O2 -fno-strict-aliasing x.c)

(gdb) disassemble main
Dump of assembler code for function main:
   0x00000000004004d0 <+0>:     sub    $0x18,%rsp
   0x00000000004004d4 <+4>:     mov    $0x4005f8,%edi
   0x00000000004004d9 <+9>:     xor    %eax,%eax
   0x00000000004004db <+11>:    movw   $0x5678,0xe(%rsp)
   0x00000000004004e2 <+18>:    movw   $0x1234,0xc(%rsp)
   0x00000000004004e9 <+25>:    mov    0xc(%rsp),%esi
   0x00000000004004ed <+29>:    callq  0x4003b8 <printf@plt>
   0x00000000004004f2 <+34>:    xor    %eax,%eax
   0x00000000004004f4 <+36>:    add    $0x18,%rsp
   0x00000000004004f8 <+40>:    retq
End of assembler dump.

错误的 (gcc -O2 -fstrict-aliasing x.c)

(gdb) disassemble main
Dump of assembler code for function main:
   0x00000000004004d0 <+0>:     sub    $0x18,%rsp
   0x00000000004004d4 <+4>:     mov    $0x12345678,%esi
   0x00000000004004d9 <+9>:     mov    $0x4005f8,%edi
   0x00000000004004de <+14>:    xor    %eax,%eax
   0x00000000004004e0 <+16>:    movw   $0x5678,0xe(%rsp)
   0x00000000004004e7 <+23>:    movw   $0x1234,0xc(%rsp)
   0x00000000004004ee <+30>:    callq  0x4003b8 <printf@plt>
   0x00000000004004f3 <+35>:    xor    %eax,%eax
   0x00000000004004f5 <+37>:    add    $0x18,%rsp
   0x00000000004004f9 <+41>:    retq
End of assembler dump.

但是如果我换成高版本的gcc,例如4.8版本,两种编译方式都没有问题,甚至加上-Wstrict-aliasing连报警都没有。只有加上-Wstrict-aliasing=1才报warning

$/opt/rh/devtoolset-2/root/usr/bin/gcc --version
gcc (GCC) 4.8.2 20140120 (Red Hat 4.8.2-15)

$/opt/rh/devtoolset-2/root/usr/bin/gcc -O2 -fno-strict-aliasing x.c

$./a.out
56781234

/opt/rh/devtoolset-2/root/usr/bin/gcc -O2 -fstrict-aliasing x.c

$./a.out
56781234

$/opt/rh/devtoolset-2/root/usr/bin/gcc -O2 -fstrict-aliasing -Wstrict-aliasing=1 x.c
x.c: In function ‘main’:
x.c:9:2: warning: dereferencing type-punned pointer might break strict-aliasing rules [-Wstrict-aliasing]
  uint16_t* const sp = (uint16_t*)&a;

网上搜了一下,Stackoverflow上有一些类似的问题 12。 我理解这应该是gcc编译器的高版本对类型转换规则的识别可能做的更加好,细节不太了解,如有看到这篇文章的朋友,求帮忙修正 :)

!!!无论如何, 如果你需要打开strict aliasing, 一定要打开Wstrict-aliasing,消除代码warning。 同时在代码上也要尽量减少这种不同类型的转换。

在MySQL移除-fno-strict-aliasing后, 也看到了一些担忧,因为mysql的codebase毕竟已经相当古老了, 而当前并没有一些静态或动态的分析工具能够找到所有违反strict aliasing规则的地方。可能存在潜在的风险。

<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、付费专栏及课程。

余额充值