打印

闲来无事,讨论个大家都容易忽略的问题

[复制链接]
9791|48
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
kiton_law|  楼主 | 2009-11-29 20:02 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 kiton_law 于 2010-4-20 23:22 编辑

如下:

    struct sta
    {
        char a;
        int   b;
    };

    struct stb
    {
        char a;
        struct sta b;
        long long c;
    };

在一个32位的C编译器中(比如ARMCC、GCC或WIN32 VC)不使用任何编译器强制对齐参数的情况下(比如align,pack等)sizeof(struct stb)= ?,不要用编译器去试,靠自己的理解回答,答错的都可以去面壁了,虽然是无聊的题,但可以考验你的基本功(此题80%的求职笔试中都有)。

-----2010年4月 添加-----

晕,突然翻到这个帖子,居然有这么多人的回复
首先谢谢43楼panyi1013的回复,你说我的说法在正常的情况下是正确的,可后面有人疑问这个“正常情况”是什么意思
这里我解答一下,我的前面帖子说的其实是一种理想情况,这种理想情况可以理解为panyi1013所说的正常情况,那就是“编译器把所有类型的变量按照他们的自然长度对齐”,当然这个自然长度是递归到变量类型中的最基本的且最长那个类型,比如你定义了一个结构A,它有结构B的子成员,而结构B的没有结构类型的成员,最长成员是long long,如果long long在这里是8字节,那么long long就是A的自然长度了,也就是8字节。
但一般情况下,编译器根据处理器的硬件体系,会选择是否按照所有类型的自然长度去对齐它们自身,比如,某些ARM编译器是把8字节自然长度的类型,拆成4字节自然长度来对齐,因为ARM寄存器就只有4字节长,8字节对齐和4字节对齐对于寄存器操作是一样的。这就是“不正常情况”了,或者叫不理想情况,如果是这种情况,则具体情形取决于编译器。
而我的研究是基于VC .Net 2003,这个编译器兼容64位,所以它不会把long long 这样的类型去做4字节对齐,这样在64位寄存器的处理器下是会产生地址异常的,而是作8字节对齐,而改编译器支持的最长基本类型的自然长度就是8了,这就意味着所有类型的自然长度都不会超过8,因此无法实验16字节长度的变量

所以结论就是,如果编译器考虑64位兼容,则不会把8字节长的基础变量拆成4字节对齐,否则就一般会这么做(除非某些强制要求的场合,比如你规定的或者某些汇编器的堆栈对齐),这也是某些同学在IAR下实验结果和我不同的原因。
这些全说明白了吧,总结如下:
1.在最高支持64位的编译器下(指支持64位寄存器),基础类型自然长度最大是8(long long)
2.在最高支持32位的编译器下,基础类型自然长度最大一般是4(long long有可能被拆到4字节对齐),可能是8
3.一个复杂类型的自然长度等于它所递归包含的最早且最长的基础类型的自然长度
4.数据总是被对齐到它所属类型的自然长度的边界地址(没有pack编译指令的情况下)
5.一个变量的长度总是其类型(不论基础还是复杂)自然长度的整数倍
沙发
mohanwei| | 2009-11-29 20:48 | 只看该作者
虽然同是跑在32位机……turboc的int可是16位的……C/C++移植问题不能一概而论的,用来当考题更是显得很那个

使用特权

评论回复
板凳
kiton_law|  楼主 | 2009-11-29 21:14 | 只看该作者
本帖最后由 kiton_law 于 2009-11-29 21:19 编辑

额,怪我没说清楚,我的意思是在32位C编译器下,turboc只能编程x86的实模式,而x86的实模式是16位的,因此turboc是16位编译器。
32位C编译器的基本数据长度和默认对齐方式都是一样的,显然可以一概而论。

而且这样的考题我觉得很好啊,又不复杂,就是基本功,如果对这个都没有很清楚的认识,作为一个搞嵌入式软件的,我觉得说不过去。

即便在16位编译器上,也仅仅是int的长度不同,其他都是一样的(我并没有给指针类型),如果此也仅仅有两种不同的答案而已,怎么不能一概而论呢。

使用特权

评论回复
地板
kiton_law|  楼主 | 2009-11-29 21:15 | 只看该作者
sizeof(struct stb)= 1
panyi1013 发表于 2009-11-29 20:41


显然不正确嘛。

使用特权

评论回复
5
neuq521| | 2009-11-30 08:12 | 只看该作者
面壁中。。。。
期待详细解释。

使用特权

评论回复
6
mohanwei| | 2009-11-30 08:29 | 只看该作者
没有优化的话,就是简单的4字节对齐原则了,int,long,float都是32位,不必说了,short int是两位,连续两个可以拼在一起;char这种单字节的则是连续4个可以拼,少于4个当也当4个……
所以struct sta是8字节,struct stb是20字节

使用特权

评论回复
7
inter_zhou| | 2009-11-30 09:24 | 只看该作者
不知道是20还是17

使用特权

评论回复
8
white5502| | 2009-11-30 09:58 | 只看该作者
面壁ing...

使用特权

评论回复
9
iciciu| | 2009-11-30 10:15 | 只看该作者
16bytes

使用特权

评论回复
10
iciciu| | 2009-11-30 10:16 | 只看该作者
1) 结构体变量的首地址能够被其最宽基本类型成员的大小所整除; 2) 结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节(internal adding); 3) 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一个成员之后加上填充字节(trailing padding)。      转发的

使用特权

评论回复
11
HWM| | 2009-11-30 10:41 | 只看该作者
类似这样的东东,好比问size(int)一样的无聊。

使用特权

评论回复
12
badbird1234| | 2009-11-30 14:38 | 只看该作者
我感觉像16
把前两个给合并它
还在面壁中

使用特权

评论回复
13
kiton_law|  楼主 | 2009-11-30 14:46 | 只看该作者
本帖最后由 kiton_law 于 2009-11-30 14:57 编辑

回12楼,当你用到的时候,就一点儿不无聊了。

正确答案 sizeof(struct stb)= 24。
为什么?
先讲讲变量的对齐,所谓变量对齐,就是把变量安排在固定的地址边界上,比如1字节地址边界可以是任意地址,2字节地址边界最后一位必须为0,4字节地址边界最后两位必须为0。

在默认的条件,变量都是被对齐到其自然边界的,所谓自然边界,对于基本数据类型来说,就是数据自然长度的地址边界,比如char型是1字节边界,int32型是4字节边界,也就是int32型的变量在默认情况下一定是被放到以0,4,8,c结尾的地址上。基本数据类型的长度在任何情况下(包括使用了pack和align)等于其自然长度或自然长度的整数倍(数组)。

结构体的自然边界等于结构体内部最长字段的自然边界。结构体的长度在默认情况下(不使用align和pack)一定是其自然边界的整数倍,比如一个结构提的自然边界是4,那么结构体的长度(使用sizeof得到的值)必然是4,8,12,16......。

因此,struct sta的自然边界是4字节地址,任何一个struct sta变量的地址都位于以0,4,8,c结尾的地址,struct sta的数据有效长度是sizeof(char)+sizeof(int) = 5,但由于结构体长度必须是自然边界的整数倍,因此sizeof(sta)=8。从另一个角度来看,在struct sta内部,sta.a被放在了起始地址,由前面知道这是一个4字节边界(当然可以做字节边界),后面的sta.b的自然长度是4,必须被放到4字节边界,因此sta.a后面被填充3个字节,整个结构提的长度就是8。

再来看struct stb,其自然边界是8(内部数据最长为long long,8字节),数据有效长度是sizeof(char)+sizeof(struct sta)+sizeof(long long) =17,由于struct stb的长度必须是8的倍数,因此sizeof(stb)=24,从另一个角度看,struct stb必然被安排在以0和8结尾的地址上,所以在struct stb内部,stb.a被放在一个8字节边界地址,后面的stb.b的自然边界是4,必须放到4字节边界地址,因此stb.a后面填充3个字节,接下来紧挨的地址是一个“8字节边界地址+12(stb.a+stb.b的长度)”的地址,这个地址不是8字节边界地址,不能存放stb.c,因此stb.b后面填充4个字节,然后存放stb.c,所以stb的长度是12+4+8=24。

在说一下#pragma pack(n),这个编译器参数是C语言通用的,只对复杂数据类型有效(结构体、类对象),pack(n)规定了结构体内的最大对齐边界为n,当结构体内的字段自然边界小于n时,该字段按照自然边界对齐,否则该字段按照n对齐。同时pack(n)也等于规定了结构体的自然边界为n,使用了pack(n)后,用sizeof求一个结构体的长度,必然是n的整数倍。

另外还有一个伪指令和数据对齐有关,就是align(n)(对于不同的编译器,该伪指令格式不同),align(n)表示把紧随其后的内存安排在n字节的边界地址上,注意align(n)这个指令只是指定起始位置,后面怎么安排取决于处理器当前的对齐设置,同时align(n)也不对结构体内的对齐方式起作用,除非该指令位于结构体内部。比如:

align(4)
char a;
int    b;

表示a变量必须放到4字节边界地址,b变量该放哪里放哪里。
如果没有align(4),而是这么写
char a;
int b;

则b变量一定是放在4字节边界地址,而a变量就不一定了,具体放哪里取决于内存排列情况。

align(n)虽然对结构体内部的对齐方式无影响,但放在结构体定义前会影响结构体长度,他规定了结构体的最小自然边界(pack是规定结构体的最大自然边界,同时对内部对齐有影响)。
比如
align(8)
struct sta
{
    char a;
    short b;
};

此时sizeof(struct sta) = 8,如果没有前面的align(8)则sizeof(struct sta) = 4。但是align不影响结构体内部的对齐方式,不管有无align参数,sta.a总是被放在结构体内部的0地址,sta.b总是被放在结构体内的2字节偏移地址(无pack参数下)。当有align(8)指令时,sta.b后面被填充进4个字节。

总结,align(n)仅仅影响紧随其后的一个变量或数据类型定义,当align(n)放在一个变量定义前时,表示将该变量放入n字节边界地址(如果变量是复杂数据类型,则不影响该变量对应的数据类型的长度),当align(n)放在一个复杂数据类型定义前时,表示指定该复杂数据类型的最小自然边界。pack(n)指定所有数据类型的最大自然边界,全局有效。

所以,记住最关键的一点,使用sizeof得到的结构体长度必然是其自然边界的整数倍,所以7楼回答的20是不对的。

PS:以上皆非转载。

使用特权

评论回复
14
kiton_law|  楼主 | 2009-11-30 14:52 | 只看该作者
一些人也许看不起这类小问题,但我认为这类小问题都研究不清楚的人,就别谈研究大问题了,虽然这些人可能正在研究着大问题。

但不管你是在做什么,做到什么层次,作为技术人员的一种力求甚解的态度,我认为是必须是要有的。

使用特权

评论回复
15
lxyppc| | 2009-11-30 15:40 | 只看该作者
长见识了
可是这种关于align,size计算方法已经看过不下3次了,就是记不住
因为还真没用到过

使用特权

评论回复
16
ejack| | 2009-11-30 17:46 | 只看该作者
我觉得LZ提的这个还是有实际意义的。现实生活中有很多未对齐引起的异常……
不过归根到底align和pack都只是伪指令,指导编译器对数据类型的解析。真正的解析结果还得看编译器。

使用特权

评论回复
17
因特网用户| | 2009-11-30 17:48 | 只看该作者
mark

使用特权

评论回复
18
HWM| | 2009-11-30 17:53 | 只看该作者
一些人也许看不起这类小问题,但我认为这类小问题都研究不清楚的人,就别谈研究大问题了,虽然这些人可能正在研究着大问题。

但不管你是在做什么,做到什么层次,作为技术人员的一种力求甚解的态度,我认为是必须是 ...
kiton_law 发表于 2009-11-30 14:52

这涉及到具体的实现问题,脱离了具体系统和环境讨论此类东西是毫无意义的。通常为了使程序具有好的移植性会定义一些意义明确的变量类型(如U8,U16和U32等),而在一个特殊的头文件中再具体定义某特定系统的变量类型。这才是解决此类问题的合理方法。所以说LZ所讨论的东东没有意义,自然就是相当的无聊。

使用特权

评论回复
19
arm_fan168| | 2009-11-30 19:15 | 只看该作者
不好意思,我算的是20,用IAR编译器实验了一下也是20。

使用特权

评论回复
20
arm_fan168| | 2009-11-30 19:24 | 只看该作者
“结构体的自然边界等于结构体内部最长字段的自然边界。”
楼主的这个理论有待实践的检验,呵呵。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

2

主题

76

帖子

0

粉丝