第七节 结构体与联合体

来源:互联网 发布:centos下安装chrome 编辑:程序博客网 时间:2024/05/18 01:10
第七节  结构体与联合体

1.1      结构体内存对齐问题
1.         这个程序本是我写来验证结构体内存对齐问题,但是我在linux系统和windows系统下的答案让我有点意外,我便将其加进本书。如程序清单7. 1所示,程序输出会是什么?
程序清单7. 1  结构体的内存对齐问题
#include<stdio.h>
struct Date{
    int    year      ;
    int    month     ;
    int    day       ;
} ;
struct DateType{
    int    year      ;
    int    month     ;
    int    day       ;
}birthday ;
struct student{
    int    iNum      ;
    char   cName[30] ;
    float  fScore    ;
    char   cSex      ;
    double menber    ;
} people ;
int main( int argc , char *argv[] )
{
printf( "sizeof(struct Date)     = %d \n\n",
sizeof( struct Date)     ) ;   
printf( "sizeof(struct DateType) = %d \n"  ,
sizeof( struct DateType) ) ;
    printf( "sizeof(birthday)        = %d \n\n", sizeof( birthday )       ) ;
    printf( "&birthday.year          = %d \n"  , &birthday.year           ) ;
    printf( "&sizeof.month           = %d \n"  , &birthday.month          ) ;
    printf( "&sizeof.day             = %d \n\n", &birthday.day            ) ;
   
printf( "sizeof(struct student)  = %d \n"  ,
sizeof( struct student)  ) ;
    printf( "sizeof(people)          = %d \n\n", sizeof( people  )        ) ;
    printf( "&people.iNum            = %d \n"  , &people.iNum             ) ;
    printf( "&people.cName           = %d \n"  , &people.cName            ) ;
    printf( "&people.fScore          = %d \n"  , &people.fScore           ) ;
    printf( "&people.cSex            = %d \n"  ,
&people.cSex             ) ;
    printf( "&people.menber          = %d \n\n", &people.menber           ) ;
    printf( "sizeof(people.menber)   = %d \n\n", sizeof( people.menber  ) ) ;
    return 0 ;
}
传统在windows下,结果大家都应该知道,我现在就直接把window下和linux下结果直接贴出来,大家看看。(如果大家对结果有质疑,大可上机试试,毕竟眼见为实。)
sizeof(struct Date)      = 12
sizeof(struct DateType) = 12
sizeof(birthday)          = 12
&birthday.year            = 4210832
&sizeof.month             = 4210836
&sizeof.day               = 4210840
sizeof(struct student)  = 56
sizeof(people)           = 56
&people.iNum            = 4210848
&people.cName           = 4210852
&people.fScore          = 4210884
&people.cSex            = 4210888
&people.menber          = 4210896
sizeof(people.menber)   = 8
请按任意键继续. . .
上面是C-Free中运行的结果,你可以试试VC等,答案依然如此。
我们再来看看linux下结果:
root@zhuzhaoqi-desktop:/home/zhuzhaoqi/C/prog1.34# ./prog
sizeof(struct Date)     = 12
sizeof(struct DateType) = 12
sizeof(birthday)        = 12
&birthday.year          = 134520948
&sizeof.month           = 134520952
&sizeof.day             = 134520956
sizeof(struct student)  = 52
sizeof(people)           = 52
&people.iNum            = 134520896
&people.cName           = 134520900
&people.fScore          = 134520932
&people.cSex            = 134520936
&people.menber          = 134520940
sizeof(people.menber)   = 8
这是linux下编译的结果。
加粗标注区域够让你吃惊吧!
说实话,看到第一眼,我也傻了。为什么,我们再看下划线标注区域,people.cSex 在windows下联系上下确实应该占用8个字节,可是,可是为什么在linux下只占用4个字节!!
原来,在linux中以4个字节为开辟单元,即不足4个开辟4个,多于4个的继续开辟4个,多出的部分 放进另一个4个字节中。
struct student{
    int    iNum      ;    /* 开辟4个字节 */
    char   cName[30] ;  /* 开辟32个字节 */
    float  fScore    ;   /* 开辟4个字节 */
    /*开辟4个字节,自己用1个字节,剩下3个,不足以存储menber */
char   cSex      ;   
    double menber    ;  /* 所以这里重新开辟4+4个字节 */
} people ;
所以我们得出的答案是:4+32+4+4+8=52。
但是,我们一直使用的windows下,以最大单元为开辟单位,即系统先检查结构中最大单位 为double 8个字节,所以以8个字节为单位。
student 在Linux和windows下内存开辟如图7. 1和图7. 2所示。


1.1      结构体在STM32的应用
1.         如程序清单7. 2所示程序是截取STM32固件库中的一段代码,请问输出是什么?
程序清单7. 2  结构体在STM32的应用
#include <stdio.h>
typedef   volatile unsigned int  vui32;
typedef struct {
  vui32  CRL;
  vui32  CRH;
  vui32  IDR;
  vui32  ODR;
  vui32  BSRR;
  vui32  BRR;
  vui32  LCKR;
} GPIO_TypeDef;
#define  GPIOA       (GPIO_TypeDef *)0x10000000
#define  GPIOLED     (GPIO_TypeDef *)GPIOA
void func (GPIO_TypeDef *GPIO)
{
   printf("GPIO->CRL = %#x\n" , &(GPIO->CRL));
   printf("GPIO->CRH = %#x\n" , &(GPIO->CRH));
   printf("GPIO->LCKR = %#x\n" , &(GPIO->LCKR));
}
int main(int argc, char *argv[])
{
    printf("sizeof(GPIO_TypeDef)  = %d\n" , sizeof(GPIO_TypeDef)) ;
    printf("GPIOLED=%#x\n" , GPIOLED);
    func(GPIOLED);
    return 0;
}
如果使用过STM32的固件函数库的话,应该对这个结构体不会陌生,STM32固件函数库就是这样,通过“大行其肆”的结构体和指针实现对一大堆寄存器的配置,在_map.h这个头文件中,定义很多这样的结构体。这样做有什么好处呢,将共同点给抽象出来了,上面7个寄存器就是每个GPIO口寄存器的共有特性,那么只要给定某一个GPIO口的映射地址,很快就可以通过这个结构体得到每个寄存器的地址。能这么做很巧的是ARM的MCU每个寄存器的偏移量都是4个字节或者2个字节,所以能使用结构体完成,如果有一天出现了3个字节的偏移量,我想此时结构体也就没辙了。
答案是:
sizeof(GPIO_TypeDef)  = 28
GPIOLED   =0x10000000
GPIO->CRL  = 0x10000000
GPIO->CRH  = 0x10000004
GPIO->LCKR = 0x10000018
请按任意键继续. . .
确实很巧妙,方便!
1.2      结构体与指针
1.         已知如下所示条件。
struct student{
    long int    num    ;
    char        *name   ;
    short int  date     ;
    char        sex      ;
    short int  da[5]    ;
}*p;
p = (student*)0x1000000 ;
那么请问,以下输出什么?
printf( "sizeof(*p)        = %d\n"  , sizeof(*p)        ) ;
printf( "sizeof(student)  = %d\n"  , sizeof(student)     ) ;
printf( "p                   = %#x\n" , p              ) ;
printf( "p + 0x200         = %#x\n" , p + 0x200        ) ;
printf( "(char*)p + 0x200       = %#x\n" , (char*)p + 0x200      ) ;
printf( "(int*)p + 0x200        = %#x\n" , (int*)p + 0x200        ) ;
第一个输出不解释,内存对齐问题,结构体指针,答案为:24。
第二个输出答案为:24。
第三个输出,为已知,答案为:0x1000000。
第四个输出,由于p此时是结构体类型指针,那么
p+0x200 = p + 0x200*sizeof(student)。
所以 p + 0x200 = p + 0x200 * 24 = 0x1000000 + 0x3000 = 0x1003000。
第五个输出,由于p被强制转换成了字符型指针,那么p + 0x200 = 0x1000200。
第六个输出同理为:p + 0x200 = 0x1000800。
1.3      联合体的存储
1.         如程序清单7. 3所示,程序输出什么?
程序清单7. 3  联合体的存储
union {
    int i ;
    struct {
       char L;
       char H;
    }Bity;
}N;
int main(int argc, char* argv[])
{
    N.i = 0x1234;
    printf("N.Bity.L = %#x\n",N.Bity.L);
    printf("N.Bity.H = %#x\n",N.Bity.H);
    return 0;
}

结构体的成员是共用一块内存,也就是说N.i和N.Bity是在同一个地址空间中。那么好办了,但是要注意,CPU是小段存储模式,所以低字节存储在低地址中,高字节存储在高地址中。那么N.Bity.L是取了低地址,也就是得到低字节,即为0x34,N.Bity.H是取了高字节,即为0x12。在电脑中,int是占4字节,所以存储方式如图10. 3所示。
其实这里有一个很巧妙的用法可以用于C51单片机中,为了与上面不重复,假设C51的存储模式是大端模式。在C51的定时器,给定时器赋初值的时候,要将高八位和低八位分别赋给模式1定时器的高位预置值和低位预置值,有这么个式子:
THx = (65536-10000)/256;
TLx = (65536-10000)%256.
那么我们就可以让这样写这个程序
union {
    unsigned int i ;
    struct {
       unsigned char H;
       unsigned char L;
    }Bity;
}N;
int main(int argc, char* argv[])
{
    ……
    N.i = 65536 - 10000;
THx = N.Bity.H ;
TLx = N.Bity.L ;
……
    return 0;
}
这样很方便并且高效地将高低位置好,其实单片机是一个很好学习C语言的载体,麻雀虽小,但是五脏俱全。
65536  10000 = 55536 = 0xD8F0。
由于在单片机中,int是占2字节,那么存储方式如图7. 4所示。


1.1      结构体在联合体中的声明
1.   如程序清单7. 4所示,请问:printf( "%d\n" , sizeof(T.N) ) ;
printf( "%d\n" , sizeof(T)    ) ;输出什么?
程序清单7. 4  结构体在联合体中的声明
union T {
    int i ;
    struct N {
       int    j ;
       float  k ;
       double m ;
    };
};
第一个结构体嘛,4+4+8=16;第二个嘛,最大元素所占内存开辟,则为:16!真的是这样吗?正确答案是:16,4!
union T {
    int i ;
    struct N {
       int    j ;
       float  k ;
       double m ;
    }A;
};
如果这个程序是这样,输出又是多少呢?正确答案是:16,16!不知道你是否知道其中的原因了呢?
1.2      结构体与联合体的内存
1.   如程序清单7. 5所示,语句printf("%d",sizeof(struct date)+sizeof(max));的执行结果是?
程序清单7. 5  结构体与联合的内存
typedef union {
long i;
int k[5];
char c;
} DATE;
struct data {
int     cat;
DATE    cow;
double dog;
}too;
DATE max;
很明显,这里考查的是联合体和结构体的内存对齐问题。联合体的内存大小是以最大类型存储内存作为依据,而结构体则是内存对齐相加。
上面例子的联合体最大是:20,所以sizeof(max) = 20 ;在结构体中,sizeof(cat) = 4 ,sizeof(cow) =  20 ,sizeof(dog) = 8 ,由于内存都是对齐的,所以siezof(struct date) = 32 .所以最终答案是:52.
#include <stdio.h>
typedef  union {
    long int  i    ;
    int       k[5] ;
    char      c    ;
}DATE ;
struct  data {
    int     cat ;
    DATE    cow ;
    double  dog ;
}too ;
int main(int argc, char *argv[])
{
    DATE max;
    printf("sizeof(cat)=%d\n",   sizeof(too.cat));
    printf("sizeof(cow)=%d\n",   sizeof(too.cow));
    printf("sizeof(dog)=%d\n\n", sizeof(too.dog));
    printf("sizeof(struct data)=%d\n",sizeof(struct data));
    printf("sizeof(max)=%d\n", sizeof(max));
    printf("sizeof(struct data)+ sizeof(max)=%d\n",
sizeof(struct data)+ sizeof(max));
    return 0;
}
运行结果是:
sizeof(cat)=4
sizeof(cow)=20
sizeof(dog)=8
sizeof(struct data)=32
sizeof(max)=20
sizeof(struct data)+ sizeof(max)=52
请按任意键继续. . .
内存对齐问题是一个比较难以理解的内存问题,因为摸不着、难以猜透。
1.3      再论联合体与结构体
1.         如程序清单7. 6所示,程序输出什么?
程序清单7. 6  再论联合体与结构体
union {
    struct {
         unsigned char c1:3;
        unsigned char c2:3;
        unsigned char c3:2;
    }s;
    unsigned char c;
}u;
int main(int argc, char *argv[])
{
    u.c = 100;
    printf("u.s.c1 = %d\n", u.s.c1);
    printf("u.s.c2 = %d\n", u.s.c2);
    printf("u.s.c3 = %d\n", u.s.c3);
    return 0;
}
这个程序考查对结构体和联合体的理解。
首先我们应该知道,u.c和u.s是在同一个地址空间中,那么u.s中存储的数据即为100。100转化为二进制为:0110  0100。由于是小端模式存储方式,那么u.s.c1取最低三位100,即为十进制的4;u.s.c2取中间三位100,即为十进制的4;而u.s.c3取最高两位01,即为十进制的1。所以输出即为:4,4,1。
原创粉丝点击