读朱兆祺攻破C语言之七---结构体、联合体

来源:互联网 发布:小米盒子刷linux 编辑:程序博客网 时间:2024/03/29 06:36

下面文章来自朱兆祺编写的《攻破c语言笔试和机试难点》的pdf,加上部分自己验证程序。在此感谢这位牛人为我们详尽讲解了C语言细节和难点问题。

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[] ){/*输出struct birthday的存储信息和占用空间信息*/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          = %x \n"  , &birthday.year           ) ;    printf( "&birthday.month         = %x \n"  , &birthday.month          ) ;    printf( "&birthday.day           = %x \n\n", &birthday.day            ) ;    printf( "&birthday+1             = %x \n\n", &birthday+1              ) ;       /*输出struct people的存储信息和占用空间信息*/printf( "sizeof(struct student)  = %d \n"  ,sizeof( struct student)  ) ;    printf( "sizeof(people)          = %d \n\n", sizeof( people  )        ) ;    printf( "&people.iNum            = %x \n"  , &people.iNum             ) ;    printf( "&people.cName           = %x \n"  , &people.cName            ) ;    printf( "&people.fScore          = %x \n"  , &people.fScore           ) ;    printf( "&people.cSex            = %x \n"  , &people.cSex             ) ;    printf( "&people.menber          = %x \n\n", &people.menber           ) ;    printf( "&people+1               = %x \n\n", &people+1                ) ;    printf( "sizeof(people.menber)   = %d \n\n", sizeof( people.menber  ) ) ;    return 0 ;}
传统在windows下,结果大家都应该知道,我现在就直接把window下和linux下结果直接贴出来,大家看看。(如果大家对结果有质疑,大可上机试试,毕竟眼见为实。)
1、下面是C-Free中运行的结果
     
由上可知:
结构体people存储详情
       
我们一直使用的windows下,以最大单元为开辟单位,即系统先检查结构中最大单位 为double 8个字节,所以以8个字节为单位.
struct student{
    int    iNum      ;    /* 开辟4个字节 */
    char   cName[30] ;   /* 开辟32个字节 */
    float  fScore    ;   /* 开辟4个字节 */
    char   cSex      ;   /*开辟8个字节,自己用1个字节,剩下7个,不足以存储menber */
    double menber    ;  /* 所以这里重新开辟4+4个字节 */
} people ;
所以我们得出的答案是:4+32+4+8+8=56

2、我们再来看看linux下结果(在ubuntu运行):
          
结构体people存储详情
         
在linux中以4个字节为开辟单元,即不足4个开辟4个,多于4个的继续开辟4个,多出的部分 放进另一个4个字节中。
struct student{
    int    iNum      ;    /* 开辟4个字节 */
    char   cName[30] ;   /* 开辟32个字节 */
    float  fScore    ;   /* 开辟4个字节 */
   char   cSex      ;   /*开辟4个字节,自己用1个字节,剩下3个,不足以存储menber */
    double menber    ;  /* 所以这里重新开辟4+4个字节 */
} people ;
所以我们得出的答案是:4+32+4+4+8=52。
小结:people.cSex 在windows下占用8个字节,可是在linux下只占用4个字节!!

1.2 结构体在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.3 结构体与指针

1. 已知如下所示条件。
int main( int argc , char *argv[] ){struct student{    long int    num    ;    char        *name   ;    short int  date     ;    char        sex      ;    short int  da[5]    ;}*p;p = (struct student*)0x1000000 ;printf( "sizeof(*p)        = %d\n"  , sizeof(*p) ) ;printf( "sizeof(student)  = %d\n"  , sizeof(struct 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*sizeof(char) = 0x1000200。
第六个输出同理为:p + 0x200*sizeof(int) = 0x1000800。

1.4      联合体的存储

1.         如程序清单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字节,所以存储方式如图所示。
          
输出结果:
N.Bity.L = 0x34
N.Bity.H =0x12
其实这里有一个很巧妙的用法可以用于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字节。


1.5 结构体在联合体中的声明

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!不知道你是否知道其中的原因了呢?
个人总结:因为第一个程序中struct N 只做了声明,未定义变量,所以不分配内存。而第二个程序中struct N 定义变量A,所以分配内存。
 

1.6结构体与联合体的内存

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.7再论联合体与结构体

1.如程序清单所示,程序输出什么?
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。
注:本人之前在富士通单片机编程过程中使用过,非常实用。

0 0
原创粉丝点击