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

时间:2022-09-05 18:24:45

下面文章来自朱兆祺编写的《攻破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中运行的结果      读朱兆祺攻破C语言之七---结构体、联合体 由上可知: 结构体people存储详情         读朱兆祺攻破C语言之七---结构体、联合体 我们一直使用的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运行):
          读朱兆祺攻破C语言之七---结构体、联合体 结构体people存储详情           读朱兆祺攻破C语言之七---结构体、联合体
在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 ) ;
}
输出结果: 读朱兆祺攻破C语言之七---结构体、联合体
第一个输出不解释,内存对齐问题,结构体指针,答案为: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字节,所以存储方式如图所示。           读朱兆祺攻破C语言之七---结构体、联合体 输出结果: 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。 注:本人之前在富士通单片机编程过程中使用过,非常实用。