例如从数据流当前位置读取一个 u64 ,内存大概率是不对齐的,一般做法是用 memcpy 把数据复制到变量上。
但有些 CPU 允许访问不对齐的内存,只是效率较低。如果程序只运行在这类 CPU 上,并且该变量读取次数极少(可能就读取一次),那么直接用指针类型转换代替 memcpy ,少一行复制代码,是不是更简洁一些?
至于运行效率,用 memcpy 复制 u64 编译时会自动展开。但内存对齐不可知的访问,编译时是否也会自动展开?如果是的话,最终效率应该都一样。
1
wudicgi 2023-03-26 13:39:09 +08:00
如果字节序和本机字节序一样的话,用 memcpy() 就挺好
要是还要转换字节序的话,写个函数按字节读取再位运算合并成 uint64_t 数值也挺好,看着很清晰 优先考虑优雅的话,效率就往后放放 |
2
HaroldFinchNYC 2023-03-26 13:43:19 +08:00
define "优雅"
|
3
xuanbg 2023-03-26 13:46:09 +08:00
为什么不对齐呢?都 2023 年了,还缺这么点内存?
|
4
leonshaw 2023-03-26 13:52:41 +08:00 via Android
用位运算
|
5
duke807 2023-03-26 13:54:01 +08:00 via Android 4
参考 linux 内核代码,用 put_unaligned 和 get_unaligned 函数
我自己经常把 linux 代码搬到 mcu 用,看这个文件最后面: https://github.com/dukelec/cdnet/blob/master/utils/cd_utils.h 使用举例: https://github.com/dukelec/msgpackel/blob/master/msgpackel_c/msgpackel.c |
6
favourstreet 2023-03-26 14:19:08 +08:00 via Android
我建议直接用对应数据类型的指针,别管其他的。怎么解引用没对齐的指针那是编译器的问题,只有给我实现了 c 语言标准定义的行为,编译器爱怎么优化怎么优化。
|
7
wudicgi 2023-03-26 14:28:05 +08:00
@favourstreet 这个还真不是编译器能完全负责的,如果所有地址都视为未对齐的,生成机器码的效率会非常低
其实一般只有处理外来的数据流时可能会遇到这种情况,特殊处理一下就好了 |
8
leonshaw 2023-03-26 14:29:17 +08:00 via Android
@favourstreet 不对齐的指针是 UB
|
9
duke807 2023-03-26 14:29:22 +08:00 via Android
@favourstreet
no no no ,在不支持非对齐访问的 cpu 上,你这样搞 cpu 会进异常的 |
10
icyalala 2023-03-26 15:15:10 +08:00
在支持非对齐内存访问的架构上,无论用 memcpy 还是自己按字节复制,
大部分编译器都会直接优化成 mov 之类的单个指令: https://godbolt.org/z/355GzW1eb (clang/x64) https://godbolt.org/z/ahhbWrGGb (gcc/x64) https://godbolt.org/z/97bav5YMz (clang/arm64) 在不支持的架构上,memcpy 有的会转为一次函数调用 call memcpy ,有的会展开: https://godbolt.org/z/3TxG7eonY (clang/arm32) 展开了 https://godbolt.org/z/hafqdcqTr (gcc/arm32) 没展开 所以如果你实际 benchmark 下来 call memcpy 性能比较差,那直接都按字节复制就行 |
11
favourstreet 2023-03-26 15:22:00 +08:00 via Android
|
12
DeltaC 2023-03-26 15:57:37 +08:00
“用指针类型转换代替 memcpy ”,可能违反 strict aliasing rule ,用的时候要注意。
这里不止存在 unaligned access 问题,你还进行了 type punning 。 符合标准的 type punning 方式有 4 个:1.符合 strict aliasing rule 的指针访问 2.union 3.memcpy 4.std::bit_cast |