结构体、共用体例题解析

例题1.数组名和结构名有什么区别?

  结构是一个标量。和其它任何标量一样,当结构名在表达式中作为右值使用时,它表示存储在结构中的值。当它作为左值使用时,它表示结构体存储的内存位置。数组名在表达式中作为右值使用时,它的值是一个指向数组第一个元素的指针。由于它的值是一个常量指针,所以数组名不能作为左值使用。

例题2.考虑下面这些声明和数据:

struct NODE {
    int a;
    struct NODE *b;
    struct NODE *c;
};
struct NODE nodes[5] = {
    {5,nodes + 3,NULL},
    {15,nodes + 4,nodes + 3},
    {22,NULL,nodes + 4},
    {12,nodes + 1,nodes},
    {18,nodes + 2,nodes + 1}      //nodes[i].b和nodes[i].c都是一个地址分别是nodes+某数的地址
};
struct NODE *np = nodes + 2;
struct NODE **npp = &nodes[1].b

对下面每个表达式求值,并写出它的值。假定nodes数组在内存中的起始位置为200,并且在这台机器上指针和整数的长度都是4字节。   

nodes         200         &nodes           200 
nodes[3].c      200         &nodes[3].c->a     200   //nodes[3].c是一个地址是nodes的地址
&nodes->a       200         nodes.a          非法 // nodes[i].a 是一个整数 nodes[3].a      12          nodes[3].c->a      5         
*nodes      { 5,nodes + 3,NULL }    *nodes.a      非法 (*nodes).a      5          nodes->a          5 nodes[3].b->b    248  *nodes[3].b->b   { 18,nodes + 2,nodes + 1 }  nodes[3].b地址是nodes+1的地址——>b即是nodes +4地址即是nodes[5].a地址 &nodes[3].a    236          &nodes[3].c     244 &nodes[3].c->a 200         & nodes->a    200 np       224          np->a    22 np->c->c->a    15          npp       216  //npp即是nodes+4这个元素的地址,相比前一个元素15多了四个字节,因为是一个指针 npp->a       非法          *npp      248 *npp->a       非法         **npp         { 18,nodes + 2,nodes + 1 } (*npp)->a     18        & np         未知       
&np->a        224        & np->c->c->a 212

1.字节对齐 什么是字节对齐?

  在C语言中,结构是一种复合数据类型,其构成元素既可以是基本数据类型(如int、long、float等)的变量,也可以是一些复合数据类型(如数组、结构、联合等)的数据单元。在结构中,编译器为结构的每个成员按其自然边界(alignment)分配空间。各个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和整个结构的地址相同。

了使CPU能够对变量进行快速的访问,变量的起始地址应该具有某些特性,即所谓的”对齐”. 比如4字节的int型,其起始地址应该位于4字节的边界上,即起始地址能够被4整除.

2. 字节对齐有什么作用

字节对齐的作用不仅是便于cpu快速访问,同时合理的利用字节对齐可以有效地节省存储空间。

对于32位机来说,4字节对齐能够使cpu访问速度提高,比如说一个long类型的变量,如果跨越了4字节边界存储,那么cpu要读取两次,这样效率就低了。但是在32位机中使用1字节或者2字节对齐,反而会使变量访问速度降低。所以这要考虑处理器类型,另外还得考虑编译器的类型。在vc中默认是4字节对齐的,GNU gcc 也是默认4字节对齐。

3. 更改C编译器的缺省字节对齐方式

在缺省情况下,C编译器为每一个变量或是数据单元按其自然对界条件分配空间。一般地,可以通过下面的方法来改变缺省的对界条件:
  使用伪指令#pragma pack (n),C编译器将按照n个字节对齐。
  使用伪指令#pragma pack (),取消自定义字节对齐方式。

另外,还有如下的一种方式:
   __attribute((aligned (n))),让所作用的结构成员对齐在n字节自然边界上。如果结构中有成员的长度大于n,则按照最大成员的长度来对齐。
   __attribute__ ((packed)),取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐。

4. 编译器是按照什么样的原则进行对齐的?

  1)数据类型按自身长度自对齐。自身长度,如char=1,short=2,int=4,double=8,。所谓自对齐,指的是该成员的起始位置的内存地址必须是它自身长度的整数倍。如int只能以0,4,8这类的地址开始

  2)结构体或类的自身对齐值:其成员中最宽基本类型自身对齐值最大的那个值。基本类型是指:char int float double 诸如结构体、数组不是基本类型

  3)指定对齐值:#pragma pack (value)时的指定对齐值value。value--只能填1 2 4 8 16

  4)数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中最小的那个值

有效对齐值N是最终用来决定数据存放地址方式的值,最重要。有效对齐N,就是表示“对齐在N上”,也就是说该数据的"存放起始地址%N=0"(结构体每个成员相对于结构体首地址的偏移量都是有效对齐值成员大小(取两者中较小值)的整数倍,如果有需要编译器会在成员之间加填充字节).而数据结构中的数据变量都是按定义的先后顺序来排放的。第一个数据变量的起始地址就是数据结构的起始地址。结构体本身也要根据自身的有效对齐值圆整(结构体总大小为结构体最宽基本类型的整数倍,如果有需要编译器会在最末一个成员之后加填充字节)。例:

#pragma pack (2)     //指定按2字节对齐
struct A
{
        char b;
        int a;
        short c;
};
#pragma pack ()     //取消指定对齐,恢复缺省对齐

第一个变量b的自身对齐值为1,指定对齐值为2,所以,其有效对齐值为1,假设A从0x0000开始,那么b存放在0x0000,符合0x0000%1=0;第二个变量,自身对齐值为4,指定对齐值为2,所以有效对齐值为2,所以顺序存放在0x0002、0x0003、0x0004、0x0005四个连续字节中,符合0x0002%2=0。第三个变量c的自身对齐值为2,所以有效对齐值为2,顺序存放在0x0006、0x0007中,符合0x0006%2=0。所以从0x0000到0x00007共八字节存放的是A的变量。又因A的自身对齐值为4,所以A的有效对齐值为2。又8%2=0,A只占用0x0000到0x0007的八个字节。所以sizeof(struct A)=8.

5.如何修改编译器的默认对齐值?

  1)在VS IDE中,可以这样修改:项目--Project属性--C/C++--代码生成--结构体成员对齐

  2)在编码时,可以这样动态修改:#pragma pack 注意:是pragma而不是progma.

 确定某个成员的实际位置应该考虑边界对齐因素,可以使用offsetof宏(定义于stddef.h)。

#define offsetof(struct_name, struct_element)(size_t)(& ( ( (struct_name *)0 )->struct_element) )可以看出来它先是将整形的0强制类型转换为结构体指针,但是值还是0不变,但表示的是结构体首地址为0,那么((struct_name *)0)->struct_element访问的就是元素的值了,我们在它前面加上取地址符&,这样得到的就是它的地址了,然后把地址强制转换为unsigned int型即可了。
表达式的值是一个size_t的值,表示这个指定成员开始存储的位置距离结构开始存储的位置偏移几个字节。

sizeof操作符能够得出一个结构的整体长度,包括因边界对齐而跳过的那些字节。

原文地址:https://www.cnblogs.com/Yang-Xin-Yi/p/13591166.html