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) );

 

 

瞧一下汇编:

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

posted @ 2011-06-24 18:35  郝壹贰叁  阅读(682)  评论(0编辑  收藏  举报