下面文章来自朱兆祺编写的《攻破c语言笔试和机试难点》的pdf,加上部分自己验证程序。在此感谢这位牛人为我们详尽讲解了C语言细节和难点问题。
1.1 结构体内存对齐问题
1. 这个程序本是我写来验证结构体内存对齐问题,但是我在linux系统和windows系统下的答案让我有点意外,我便将其加进本书。如程序清单7. 1所示,程序输出会是什么? 程序清单7. 1 结构体的内存对齐问题#include<stdio.h>传统在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
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 ;
}
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字节。