C语言中结构化数据(变量,指针,数组,字符串,结构体和联合)的内存表示

结构化数据表示

一、内存地址:

所有数据都存储在内存中
内存是一个有编号的字节序列,以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

猜你喜欢

转载自blog.csdn.net/weixin_47138646/article/details/122017208