最近搞了几个传感器,各个的数据格式都不太一样,记录一下处理数据的tips。
1 注意是否溢出
遇到这样的情况:原始数据本身是8bit,然而需要加上offset才能得到真实数据,一疏忽用了uint8_t来存放数据,显然就会存在溢出的风险。
2 使用struct
比如原始数据是这样的:
每个data是12bit,byte1是data1的高8位,byte2是data2的高8位,byte3[3:0]是data1的低4位,byte3[7:4]是data2的低4位,以此类推。
那么可以定义这样的struct来循序处理数据:
typedef struct
{
uint8_t high[2];
uint8_t low4;
}Rawdata;
拼接数据的时候就可以类似这样写:
uint16_t data[2];
Rawdata *raw; //这里用指针便于移动
//数据读取...
data[0] = (raw->high[0] << 4) | (raw->low4 & 0xf);
data[1] = (raw->high[1] << 4) | (raw->low4 >> 4);
3 数据左移是否会溢出呢?
来看这样的例子:
uint8_t x = 0xf;
uint8_t y = 0x5;
uint16_t p = (x << 8) | y;
uint16_t q = ((unsigned int)x << 8) | y;
那么p和q有区别吗?
放到VS中跑一下,发现两条语句的汇编代码都是相同的:
movzx eax,byte ptr [x]
shl eax,8
movzx ecx,byte ptr [y]
or eax,ecx
mov dword ptr [q],eax
先把x放到eax寄存器中,再进行移位。鉴于eax本身是个32bit的寄存器,足以胜任左移8bit了。
但是如果是这样呢,x左移32bit:
//long long int 是64bit
long long int p = (x << 32) | y;
long long int q = ((long long int)x << 32) | y;
得到的结果是:
p 0x000000000000000f
q 0x0000000f00000005
显然q才是想要的结果。再来对比一下汇编:
//long long int p = (x << 32) | y;
movzx eax,byte ptr [x]
shl eax,20h
movzx ecx,byte ptr [y]
or eax,ecx
cdqe
mov qword ptr [p],rax
//long long int q = ((long long int)x << 32) | y;
movzx eax,byte ptr [x]
shl rax,20h
movzx ecx,byte ptr [y]
or rax,rcx
mov qword ptr [q],rax
在计算p时,用的还是32bit的eax寄存器。根据CSAPP的说法:
在许多机器上,当移动一个w位的值时,移位指令只考虑位移量的低 l o g 2 w log_2w log2w位,因此实际上的位移量就是通过计算k mod w得到的。
因此左移32位实际上是移动0位。
而计算q时,使用了rax这个64bit的寄存器,所以结果是理想的。所以在左移时限定数据类型是有帮助的。However,以此推论,左移64bit的话rax寄存器也会不够用了。
P.S. 在计算p时执行了cdqe这条指令,即
CDQE copies the sign (bit 31) of the doubleword in the EAX register into the high 32 bits of RAX.
——Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 2A
使用eax的bit 31拓展rax高32位的所有位,那么如果是将x左移31位,显然结果会很有趣:
p 0xffffffff80000005
BTW,还有一种情形:
uint8_t x = 3;
uint8_t m = (x << 8);
uint16_t n = (x << 8);
看看汇编代码实际是如何做的:
//uint8_t m = (x << 8);
movzx eax,byte ptr [x]
shl eax,8
mov byte ptr [m],al
//uint16_t n = (x << 8);
movzx eax,byte ptr [x]
shl eax,8
mov dword ptr [n],eax
区别在于最后一步是mov byte ptr
还是 mov dword ptr
,如果是mov byte ptr
,显然数据高8位就被舍弃了,故而结果是:
m 0x00
n 0x00000300
4 符号数拼接
假如原始数据是12bit的符号数,即bit 11为符号位,可以这样:
uint16_t data;
//数据读取...
if ((data >> 11) == 1) //判断符号
data = data | 0xf000; //用符号位扩展bit[15:12]
5 无符号数相减溢出
uint8_t x = 3;
uint8_t y = 5;
uint8_t u = x - y; //u的值为254,即0xfe
int8_t w = x - y; //w的值为-2,也即0xfe,从存储的数据看u和w实际上是相同的
举个栗子,假如已知x和y实际上是4bit的数据,那么相减得到的值大于15显然就是溢出了。
符号数和无符号数的运算细节很有可能导致难以觉察到的错误,有必要复习一下CSAPP的Chapter 2。