最近在调试一个Camera ISP OTP校准的问题,在开发过程中,要将2个字节的数据组合成16bit的数据。一开始我以为只要是一大块内存,我们告诉它类型,那么编译器就会自动分配对应的内容。例如:下面图1中连续的内容,加入这里我把uint8_t temp[4] = {0x20,0x33,0x44,0x55},现在我想把它放到一个结构类型的数据中,假如这里数据结构是:
struct test{
uint16_t a;
uint16_t b;
}
这里我们有struct test *arm = (struct test *)test;这样一行代码,大家猜猜arm中对应的a,b域分别是多少。一开始我以为就是arm->a = 0x2033,arm->b = 0x4455;
实际存放状态 目的状态
经过实际代码验证,由于存在大小端问题,它会将低地址的数据放到高位,高地址的数据放到低位。如下试验代码
试验代码1
#include<stdio.h>
unsigned char temp[] = {0x20,0x33,0x44,0x55};
struct test{
unsigned short a;
unsigned short b;
};
int main(){
struct test * arm = (struct test *)temp;
printf("A:0x%x\n",arm->a);
printf("B:0x%x\n",arm->b);
}
打印结果:看到结果的瞬间,大家都明白了吧。
A:0x3320
B:0x5544
实验代码2
实验2重要研究移位操作对赋值的影响,如下面代码标识出的。
#include<stdio.h>
typedef struct {
unsigned short a;
unsigned short b;
unsigned short c;
}data_test;
unsigned char temp[6]={0x11,0x22,0x33,0x44,0x55,0x66};
void test(unsigned char *in){
data_test dta;
unsigned char *in_data = in;
dta.a = (*in_data & 0xff) <<8 + (*(in_data+1)&0xff); //---------------------
dta.b = (*(in_data+2) & 0xff) <<8 + (*(in_data+3)&0xff); //---------------------
dta.c = (*(in_data+4) & 0xff) <<8 + (*(in_data+5)&0xff); //---------------------
printf("armwind,0x%x\n",dta.a);
printf("armwind,0x%x\n",dta.b);
printf("armwind,0x%x\n",dta.c);
int i;
for(i=0;i<6;i++){
printf("hehe:0x%x\n",*(in_data +i));
}
}
int main(){
test(temp);
printf("char:%d\n",sizeof(unsigned char));
printf("short:%d\n",sizeof(unsigned short));
}
运行结果:
armwind,0x4400
armwind,0x3000
armwind,0x4000
hehe:0x11
hehe:0x22
hehe:0x33
hehe:0x44
hehe:0x55
hehe:0x66
char:1
short:2
上面的代码很简单,就是有3行非常不解,字面上看没什么错误,但为什么打出来的结果和我们预期的,有这么大差异呢。为此我将代码反汇编了,在汇编代码中我找到了原因。请看汇编代码片段。
dta.a = (*in_data & 0xff) <<8 + (*(in_data+1)&0xff);
dta.b = (*(in_data+2) & 0xff) <<8 + (*(in_data+3)&0xff);
dta.c = (*(in_data+4) & 0xff) <<8 + (*(in_data+5)&0xff);
汇编代码片段:
void test(unsigned char *in){
4004f4: 55 push %rbp
4004f5: 48 89 e5 mov %rsp,%rbp
4004f8: 53 push %rbx
4004f9: 48 83 ec 38 sub $0x38,%rsp
4004fd: 48 89 7d c8 mov %rdi,-0x38(%rbp) //这里申请临时变量堆栈,用来
data_test dta;
unsigned char *in_data = in;
400501: 48 8b 45 c8 mov -0x38(%rbp),%rax
400505: 48 89 45 d8 mov %rax,-0x28(%rbp) //*in_data = in;
dta.a = (*in_data & 0xff) <<8 + (*(in_data+1)&0xff);
400509: 48 8b 45 d8 mov -0x28(%rbp),%rax //取到in_data指针
40050d: 0f b6 00 movzbl (%rax),%eax //拿到*in_data值
400510: 0f b6 d0 movzbl %al,%edx //;(*in_data)&0xff 取低8位 --------------------------------------->前面一半计算的结果在edx中。
400513: 48 8b 45 d8 mov -0x28(%rbp),%rax //开始(*(in_data+1)&0xff),这是拿到in_data指针。
400517: 48 83 c0 01 add $0x1,%rax //in_data 进行+1操作
40051b: 0f b6 00 movzbl (%rax),%eax //;(*(in_data +1))
40051e: 0f b6 c0 movzbl %al,%eax //(*(in_data+1)&0xff)计算的结果取低8位,放到eax,高位补0.
400521: 83 c0 08 add $0x8,%eax //;这里直接就是将eax+8了,这里可以移位的数量,之前eax存放的是高8位数据------------>这个地方就出现问题了。不应该直接加eax。
400524: 89 d3 mov %edx,%ebx //;ebx中存放的是高8位数据。
400526: 89 c1 mov %eax,%ecx //
400528: d3 e3 shl %cl,%ebx //;高8位数据逻辑左移动cl个寄存器。
40052a: 89 d8 mov %ebx,%eax
40052c: 66 89 45 e0 mov %ax,-0x20(%rbp)
dta.b = (*(in_data+2) & 0xff) <<8 + (*(in_data+3)&0xff);
400530: 48 8b 45 d8 mov -0x28(%rbp),%rax
400534: 48 83 c0 02 add $0x2,%rax
400538: 0f b6 00 movzbl (%rax),%eax
40053b: 0f b6 d0 movzbl %al,%edx
40053e: 48 8b 45 d8 mov -0x28(%rbp),%rax
400542: 48 83 c0 03 add $0x3,%rax
400546: 0f b6 00 movzbl (%rax),%eax
400549: 0f b6 c0 movzbl %al,%eax
40054c: 83 c0 08 add $0x8,%eax //同理,这里 (*(in_data+3)&0xff) + 8操作
40054f: 89 d3 mov %edx,%ebx
400551: 89 c1 mov %eax,%ecx
400553: d3 e3 shl %cl,%ebx
400555: 89 d8 mov %ebx,%eax
400557: 66 89 45 e2 mov %ax,-0x1e(%rbp)
dta.c = (*(in_data+4) & 0xff) <<8 + (*(in_data+5)&0xff);
40055b: 48 8b 45 d8 mov -0x28(%rbp),%rax
40055f: 48 83 c0 04 add $0x4,%rax
400563: 0f b6 00 movzbl (%rax),%eax
400566: 0f b6 d0 movzbl %al,%edx
400569: 48 8b 45 d8 mov -0x28(%rbp),%rax
40056d: 48 83 c0 05 add $0x5,%rax
400571: 0f b6 00 movzbl (%rax),%eax
400574: 0f b6 c0 movzbl %al,%eax
400577: 83 c0 08 add $0x8,%eax //同理,这里 (*(in_data+5)&0xff) + 8操作,这是无效的。
40057a: 89 d3 mov %edx,%ebx
40057c: 89 c1 mov %eax,%ecx
40057e: d3 e3 shl %cl,%ebx
400580: 89 d8 mov %ebx,%eax
400582: 66 89 45 e4 mov %ax,-0x1c(%rbp)
...................
总结:以后在将高八位,低八位进行拼接的时候,最好使用一个中间变量,要不然编译器会直接舍弃调低8位,导致运算失效。切记,切记!!!!