More about Struct&Union
技巧一、小结构体地址推断大结构体地址
关于进程调度的thread_union结构,有些趣味。

这里做个小模拟:
int main(void)
{
834c: e24dd020 sub sp, sp, #32 ; 0x20 //sp指针指向地址最低位,栈增长的最大界限位置
union {
struct thread_info thread_info;
unsigned long stack[8];
}uni_x;
uni_x.thread_info.a = 1;
8350: e3a03001 mov r3, #1 ; 0x1
8354: e58d3000 str r3, [sp] //这里thread_info.a处在地址最低位
uni_x.thread_info.b = 2;
8358: e3a03002 mov r3, #2 ; 0x2
835c: e58d3004 str r3, [sp, #4] //thead.b的位置
uni_x.stack[7] = 7; //将stack数组的index由高到底操作,实现向低地址方向的栈增长
8360: e3a03008 mov r3, #7 ; 0x7
8364: e58d3020 str r3, [sp, #28]
uni_x.stack[6] = 6; //7,6...1,0,当然这里要考虑thread_info的大小,至少uni_x.stack[0]会覆盖thread_info
8368: e3a03007 mov r3, #6 ; 0x6
836c: e58d301c str r3, [sp, #24]
return 0;
8370: e3a03000 mov r3, #0 ; 0x0
}
“一个结构,两种用法”,重要的是思想,细细咀嚼,回味无穷。
再说下结构体,不得不提的是那个内核界早已闻名的巨星:宏contain_of (),先贴个代码:
#define mem_to_obj(ptr, type, member) \
( (char*)ptr - (char*)(&((type*)0)->member) )
int main(void)
{
struct test_struct {
int a;
int b;
};
struct test_struct test;
test.a = 1;
test.b = 2;
int *bp = &test.b;
struct test_struct *p = &test;
struct test_struct *q = NULL;
q = mem_to_obj(&test.b, struct test_struct, b); //通过结构体中b地址,找到该结构体的地址
return 0;
}
宏展开后如下,简单明了,无需多言。
q = mem_to_obj(&test.b, struct test_struct, b);
q = ( (char*)&test.b - (char*)(&((struct test_struct*)0)->b) );
q = ( (char*)&test.b - (char*)(&((struct test_struct*)0)->b) );
瞧一下汇编:
int main(void)
{
834c: e24dd018 sub sp, sp, #24 ; 0x18
int a;
int b;
};
struct test_struct test;
test.a = 1;
8350: e3a03001 mov r3, #1 ; 0x1
8354: e58d3004 str r3, [sp, #4]
test.b = 2;
8358: e3a03002 mov r3, #2 ; 0x2
835c: e58d3008 str r3, [sp, #8]
int *bp = &test.b;
8360: e28d3004 add r3, sp, #4 ; 0x4
8364: e2833004 add r3, r3, #4 ; 0x4
8368: e58d300c str r3, [sp, #12]
struct test_struct *p = &test;
836c: e28d3004 add r3, sp, #4 ; 0x4
8370: e58d3010 str r3, [sp, #16]
struct test_struct *q = NULL;
8374: e3a03000 mov r3, #0 ; 0x0
8378: e58d3014 str r3, [sp, #20]
q = mem_to_obj(&test.b, struct test_struct, b);
837c: e28d3004 add r3, sp, #4 ; 0x4 //r3已经是test_struct的地址,但之后的两条汇编,感觉无厘头
8380: e2833004 add r3, r3, #4 ; 0x4 //在试着增加test_struct中的变量后发现,仍然是add x,再sub x
8384: e2433004 sub r3, r3, #4 ; 0x4 //可能编译器不是万能的缘故,何况mem_to_obj也得浪费你1s的理解时间
8388: e58d3014 str r3, [sp, #20]
return 0;
838c: e3a03000 mov r3, #0 ; 0x0
}
技巧二、结构体的屁屁指针
最后再介绍个结构体中运用arr[0]的小技巧。
arr[0]是什么?到底有还是没有?
它确实没有,没有占内存空间,当你用它的时候,它却能发挥点作用。ho~ho~有点像鬼魂~
int main(void)
{
8380: e52de004 push {lr} ; (str lr, [sp, #-4]!)
8384: e24dd014 sub sp, sp, #20 ; 0x14
struct test_struct {
int a;
int b;
int c[0]; // c是个指针,准确的说是半个指针,或者就是个int * const c,你懂de
}test;
int d = 8; // 根据编译器对变量的分配规则,int d自然排在了test_struct结构体之后,而且是紧挨着
8388: e3a03008 mov r3, #8 ; 0x8
838c: e58d300c str r3, [sp, #12]
printf("1: d = %d\n", d); // 这里d当然等于8
8390: e59f003c ldr r0, [pc, #60] ; 83d4 <main+0x54>
8394: e59d100c ldr r1, [sp, #12]
8398: ebffffc8 bl 82c0 <_init+0x48>
test.a = 1;
839c: e3a03001 mov r3, #1 ; 0x1
83a0: e58d3004 str r3, [sp, #4]
test.b = 2;
83a4: e3a03002 mov r3, #2 ; 0x2
83a8: e58d3008 str r3, [sp, #8]
//test.c = NULL; /*作为半个指针,当然也就是不能被赋值了,若赋值,就如下报错*/
/*main.c:14:12: error: incompatible types when assigning to type ‘int[]’ from type ‘void *’*/
test.c[0] = 10; // 虽不能被赋值,但却能指引其他地儿赋值:给紧挨着test_struct的PP的地方赋值10
83ac: e3a0300a mov r3, #10 ; 0xa
83b0: e58d300c str r3, [sp, #12]
printf("2: d = %d\n", d); // 很不幸,d就在test_struct的PP的地方,d被间接改变
83b4: e59f001c ldr r0, [pc, #28] ; 83d8 <main+0x58>
83b8: e59d100c ldr r1, [sp, #12]
83bc: ebffffbf bl 82c0 <_init+0x48>
return 0;
83c0: e3a03000 mov r3, #0 ; 0x0
}
结构体中运用arr[0]常用于指向struct's PP的功能。先可意会,实践中才能深入理解。
说到此,想起个事,被调函数中定义的"变量x"会随着被调函数的return而其资源一并销毁。若在销毁后正巧有一个指针能重新操作那个x的位置,是否会发生编译器编译报错呢? 实践的结果是不会。指针指向的x的位置,相对于整个程序来说,仍认为是合法,但最好不要这么做,毕竟不按规则出牌……要不见红涨停,要不套牢割肉……

浙公网安备 33010602011771号