目录
结构化数据表示一、内存地址:二、全局变量和局部变量的内存布局三、数据在内存中的表示四、数组在内存中的表示五、字符串在内存中的表示六、结构和联合在内存中的表示结构化数据表示
一、内存地址:
所有数据都存储在内存中
内存是一个有编号的字节序列,以0x开始。那个编号计算内存的地址。
通常来说,经过编译后变量名和变量类型都不见了,取而代之的是内存地址。
二、全局变量和局部变量的内存布局
通过一个例子,观察它们的内存地址
#include<stdio.h>/*全局未初始化变量*/int global_int1;int global_int2;/*全局初始化变量*/int global_int3=1;int global_int4=1;int main(){/*局部未初始化变量*/int local_int1;int local_int2;/*局部初始化变量*/int local_int3=1;int local_int4=1;printf("全局变量的值:%d %d %d %d\n",global_int1,global_int2,global_int3,global_int4);printf("局部变量的值:%d %d %d %d\n",local_int1,local_int2,local_int3,local_int4);printf("初始化的全局变量地址:%#x %#x\n",&global_int3,&global_int4);printf("未初始化的全局变量地址:%#x %#x\n",&global_int1,&global_int2);printf("局部变量地址:%#x %#x %#x %#x\n",&local_int1,&local_int2,&local_int3,&local_int4);}
运行结果
全局变量的值:0 0 1 1局部变量的值:35 8 1 1初始化的全局变量地址:0x402000 0x40未初始化的全局变量地址:0x40506c 0x405068局部变量地址:0x61fefc 0x61fef8 0x61fef4 0x61fef0
观察运行结果中的值,我们发现:
未初始化的全局变量似乎以某种方式初始化为0,但对于局部变量,并没有进行初始化。
接下来我们观察地址的值,发现:
对于局部变量:
所有的局部变量是连续分配的,聚集在一起
变量往低地址方向增长。
对于全局变量:
初始化的那些变量在一个集群中,未初始化的在另外一个集群中。
未初始化的变量往低地址方向增长,初始化的变量往高地址方向增长。
解释:一次增长4,是因为int类型占4个字节大小。
三、数据在内存中的表示
首先我们需要认识到一点,指针是什么?
指针存储的便是内存地址,这也是c语言的灵活之处。关于指针的详细讲解,可以查看C语言之指针
接着我们来思考一个问题,我们可以用一个char类型来读int型数据吗?
答案是可以的,因为它们在硬件中都用原始位表示。
这和数据在内存中的存储有关,我们来看到一个例子:
#include<stdio.h>int main(){int i=0x8041;char *p;p=(char*)&i;printf("%#x\n",*p);}
它的结果会是什么呢?
//运行结果0x41
怎么回事呢?
变量i在内存中如下所示:
而char字符型只占一个字节,所以只读取到了41;
测试一下,如果将值变成0x12345678;
#include<stdio.h>int main(){int i=0x12345678;char *p;p=(char*)&i;printf("%#x\n",*p);}
运行结果为
0x78
编码
它指定了值到位的映射,也就是哪个位序列表示哪个整数,哪个位序列表示哪个字符
它指定了特定类型需要多少位,比如说int整型,4个字节,需要32位
取决于计算机和我们使用的编码
编译器负责完成这项工作,并为我们处理这些细节。
四、数组在内存中的表示
数组定义
统一类型的元素序列
内存分配
在内存中按递增顺序排列数组元素
#include<stdio.h>int main(){int a=0;int intArray[5]={1,2,3,4,5};int b=6;}
内存表示:
指针VS数组
intArray+N=&(intArray[N])
intArray[N]=*(intArray+N)
多字节类型数组
看一个例子:
#include<stdio.h>int main(){int intArray[10]={1,2,3,4,5,6,7,8,9,10};int *p=(int*)((char*)intArray+7);printf("%#x\n",*p);}
运行结果:
0x300
怎么回事呢?
有空再来写二维数组
五、字符串在内存中的表示
在c中,没有字符串类型
字符串是由字符’\0’(也称为空字符)结尾的字符数组
常见的标准的字符串库如char *strcat(char *dest,char *src)
,函数中传入的是字符串数组首地址,那么为什么不用传入数组长度呢?个人理解,这就是字符串是由字符’\0’(也称为空字符)结尾的原因之一,可以用以遍历。
例子:
#include<stdio.h>int main(){int i;char a[4]="hi?";char b[3]="hi?";//错误写法,很危险for(i=0;i<4;i++){printf("%#x:%c(%d)\n",&a[i],a[i],a[i]);}printf("\n");for(i=0;i<3;i++){printf("%#x:%c\n",&b[i],b[i]);}puts(b);}
运行结果
0x61fef8:h(104)0x61fef9:i(105)0x61fefa:?(63)0x61fefb: (0)0x61fef5:h0x61fef6:i0x61fef7:?hi?hi?
怎么回事呢?
六、结构和联合在内存中的表示
聚合数据类型能够同时存储超过一个的单独数据。C提供了两种类型的聚合数据类型,数组和结构。
数组是相同类型的元素集合,它的每个元素是通过下标引用或指针间接访问来选择的。
结构也是一些值的集合,这些值被称为它的成员,但一个结构的各个成员可能具有不同的类型。
数组元素下标可以通过下标访问是因为数组元素的长度相同。
但是,在结构体中情况并非如此。由于一个结构的成员长度不同,所以不能使用下标来访问它们,相反每个结构成员都有自己的名字,它们是通过名字访问的。
在讨论结构的内存之前,我们先来看到结构的声明:
例子一:这个声明创建了一个名叫x的变量,它包含三个成员:一个整数、一个字符和一个浮点数。
struct{int a;char b;float c;}x;
例子二:这个声明创建了y和z。y是一个数组,它包含了20个结构。z是一个指针,它指向这个类型的结构。
struct{int a;char b;float c;}y[20],*z;
这两个结构的的成员变量一样,它们是同一种类型吗?
答案是否,这两个声明被编译器当作两种截然不同的类型。也就是说
z=&x
是非法的。
那么问题来了,如果我想在后续继续使用之前声明过的类型,是不是必须在一个单独的声明中创建呢?
答案是否的,可以使用标签。标签允许多个声明使用同一个成员列表。如下所示:
struct SIMPLE{int a;char b;float c;};struct SIMPLE x;struct SIMPLE y[20],*z;z=&x;//这时候是合法的;
不过声明结构的一个更良好的习惯是使用typedef
创建一种新类型,如下所示:
typedef struct{int a;char b;float c;}SIMPLE;//此时SIMPLE是个类型名而不是结构标签SIMPLE x;SIMPLE y[20],*z;
进入正题:我们来看看结构体在内存中的实际存储
先来看到一个例子
#include<stdio.h>int main(){typedef struct {char a;char b;int c;double d;}test;test x;printf("%#x\n%#x\n%#x\n%#x\n",&x.a,&x.b,&x.c,&x.d);printf("%d",sizeof(x));}
运行结果:
0x61fef00x61fef10x61fef40x61fef816
为什么会是这样呢?那就需要搞清楚结构中内存存储的对齐规则:
1.编译器按照成员列表的顺序一个接一个地为每个成员分配空间
2.结构体的对齐与其元素的最大对齐相同
3.一个结构体的大小是其对齐方式的倍数
4.K字节大小的数据必须要存储在K的整数倍的地址上
改变一下这个例子:
#include<stdio.h>int main(){typedef struct {char a;char b[4];int c;double d;}test;test x;printf("%#x\n%#x\n%#x\n%#x\n",&x.a,&x.b,&x.c,&x.d);printf("%d",sizeof(x));}
0x62fe000x62fe010x62fe080x62fe1024
那么为什么要这样对齐呢?不会浪费了空间吗?
和CPU处理内存的方式有关,这样可以提高系统的性能,加快访问数据的速度。
那么联合是什么?
和结构体不同,联合中的各个成员共享一块内存,使用时只会选择其中的一个。
结构体的在内存中的存储
所占的空间取决于联合中的最大元素
看一个例子
#include<stdio.h>int main(){typedef union{char a;char b[4];int c;double d;}test;test x;printf("%#x\n%#x\n%#x\n%#x\n",&x.a,&x.b,&x.c,&x.d);printf("%d",sizeof(x));}
运行结构:
0x61fef80x61fef80x61fef80x61fef88