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的位置,相对于整个程序来说,仍认为是合法,但最好不要这么做,毕竟不按规则出牌……要不见红涨停,要不套牢割肉……