数据拼接与处理Tips

最近搞了几个传感器,各个的数据格式都不太一样,记录一下处理数据的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。

猜你喜欢

转载自blog.csdn.net/u013213111/article/details/104584936
今日推荐