结构化数据表示
一、内存地址:
所有数据都存储在内存中
内存是一个有编号的字节序列,以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 0x402004
未初始化的全局变量地址: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:h
0x61fef6:i
0x61fef7:?
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));
}
运行结果:
0x61fef0
0x61fef1
0x61fef4
0x61fef8
16
为什么会是这样呢?那就需要搞清楚结构中内存存储的对齐规则:
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));
}
0x62fe00
0x62fe01
0x62fe08
0x62fe10
24
那么为什么要这样对齐呢?不会浪费了空间吗?
和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));
}
运行结构:
0x61fef8
0x61fef8
0x61fef8
0x61fef8
8