学习C指针之C是怎么使用内存的

10 篇文章 0 订阅

虚拟地址

现在的电脑提供多任务的环境,可以同时运行多个应用程序。假设同时运行两个程序,然后打印各自的变量地址,会出现一样的结果吗?

比如运行下面这个程序:

#include<stdio.h>

int main() {
    int a;
    char buf[256];

    printf("&a...%p\n", &a);

    printf("input initial value..\n");
    fgets(buf, sizeof(buf), stdin);

    sscanf(buf, "%d", &a);

    for (;;) {
        printf("a..%d\n", a);
        getchar();
        a++;
    }
    return 0;
}


分别同时开两个窗口,得到这两个结果。

&a...0061FF2C
input initial value..
10
a..10

a..11

a..12

a..13
&a...0061FF2C
input initial value..
20
a..20

a..21

a..22

a..23

结果显示:存放变量的地址是一样的,但是里边的内容确实不一样的。通过这样的实验发现,在这个环境中,使用printf()输出的指针的时候,打印的并不是物理内存地址本身。

现在的操作系统都会给应用程序的每一个进程分配独立的“虚拟地市空间”。这不是c语言的关系,是CPU和操作系统协同工作的结果。

c的内存的使用方法

c的变量的种类

1,全局变量:函数之外声明的变量,默认的会成为全局变量。全局变量在任何地方都是可见的。当程序被分割成多个源代码文件进行编译时,声明为全局变量的变量也是可以从其他源代码文件中引用的。

2,文件内部的静态变量:就算对于像全局变量那样被定义在函数外面的变量,一旦添加了static作用域就只限定在当前所在的源代码文件中。

通过static限定的变量(包括函数),对于其他源代码是不可见的。

3,局部变量:局部变量是指在函数中声明的变量。局部变量只能包含在它的声明的语句块中被引用。

局部变量通常在它所在的语句块结束的时候被释放。如果你不想释放某一个局部变量,可以在局部变量上加上static进行声明。

C的变量的存储期

1,静态存储期

全局变量、文件内的静态变量、指定的静态局部变量都具有静态存储期。

静态存储期的变量的寿命从程序的运行时开始,到程序关闭时结束。换句话说,静态变量一直存在于内存的同一个地址上。

2,自动存储期

没有指定static的局部变量,持有自动存储期。这样的变量被称为自动变量。持有自动存储期的局部变量,在程序运行时进入他所在的语句块时被分配内存区域,该语句块执行结束后这片内存区域被释放。

输出地址

一些变量、函数等地址内存是如何配置的呢?

请看下面的例子:

#include<stdio.h>
#include<stdlib.h>

int global_variable;
static int file_static_variable;

void func1(void) {
    int func1_variable;
    static int func1_static_variable;

    printf("&func1_variable..%p\n", &func1_variable);
    printf("&func1_static_varibale..%p\n", &func1_static_variable);
}

void func2(void) {
    int func2_variable;
    printf("&func2_variable..%p\n", &func2_variable);
}

int main(void) {
    int *p;
    char buf[256];

    /*输出指向函数的指针*/
    printf("&func1..%p\n", func1);
    printf("&func2..%p\n", func2);

    /*输出字符串常量的地址*/
    printf("string literal..%p\n", "abc");

    /*输出全局变量的地址*/
    printf("&globa_variable..%p\n", &global_variable);

    /*输出文件内static变量的地址*/
    printf("&file_static_variable..%p\n", &file_static_variable);

    /*输出局部变量*/
    func1();
    func2();

    /*通过malloc()申请的内存区域地质*/
    p = malloc(sizeof(int));
    printf("malloc address..%p\n", p);
    
    return 0;
}


我们得到这样的结果,这样的结果有什么样的意义呢?往下看。

&func1..00401460
&func2..00401490
string literal..004050C2
&globa_variable..00407078
&file_static_variable..00407020
&func1_variable..0061FDFC
&func1_static_varibale..00407024
&func2_variable..0061FDFC
malloc address..00864F40

&函数1的地址                     ..00401460
&函数2的地址                     ..00401490
字符串常量的地址              ..004050C2


&全局变量的地址               ..00407078
&文件内静态变量的地址   ..00407020
&函数1的自动变量             ..0061FDFC
&函数内的静态变量           ..00407024
&函数2的自动变量             ..0061FDFC
利用函数分配的内存地址  ..00864F40

 

通过观察,我们发现“指向函数的指针”和“字符串常量”被配置在非常相近的内存区域。

函数内static变量、文件内static变量、全局变量等这些内存区域,也是被配置在非常近的内存区域。

两个函数的自动变量被分配了完全相同的内存地址。

那个malloc函数分配的内存区域,和自动变量的区域离得很远。

指向函数的指针

比如有如下函数原型:

int func(double d);

保存指向此函数的指针的变量的声明如下:

int(*func_p)(double);

然后写成下面,就可以通过指针调用func:

int(*func_p)(double);

func_p = func;

func_p(0.5);

后面还有指向函数的指针的数组,像下面这样:

int (*func_table[])(double) = {

       func0;

       func1;

       func2;

       func3;

       func4;

       func5;

};

func_table[i](0.5);   //调用func_table[i]的函数,参数为0.5;

利用malloc()来进行动态内存分配(堆)

malloc()根据参数指定的尺寸来分配内存块,返回内存块初始位置的指针,经常被用于动态分配结构体的内存区域、分配前还不知道大小的数组的内存区域等。

 p = malloc(size);

free(p);

像这样能够动态的进行内存分配,并且可以通过任意的顺序释放的记忆区域,称为堆。

malloc()主要有一下的使用范例:

1,动态分配结构体

假设用下面这样的结构体管理一本书的数据:

typedef struct 
{
    char title[64];
    int price;
    char isbn[32];
} BOOKDATA;

 在这种情况下,需要动态的申请地址。通过下面的方式就可以在运行时分配BOOKDATA的内存区域。

typedef struct 
{
    char title[64];
    int price;
    char isbn[32];
} BOOKDATA;

BOOKDATA *book_data_p;

book_data_p = malloc(sizeof(BOOKDATA));

2,分配可变长数组

在上边的例子中,当结构体中的一个数据很大时怎么办?可以用malloc()实现。比如说书的title太长,可以将上边的改成:

typedef struct 
{
    char *title;
    int price;
    char isbn[32];
} BOOKDATA;

BOOKDATA *book_data_p;

book_data_p->title = malloc(sizeof(char) * len);   //这里的len表示标题字符数+1;

//此时如果你想引用title中的某一个特定字符,可以写成:
book_data_p->title[i];

malloc()中发生了什么

这个函数的大体实现是:从操作系统中一次性的取得比较大的内存,然后将这些内存“零售给”应用程序。

内存布局对齐

假设有这么一个结构体:

typedef struct 
{
    int int1;
    double double1;
    char char1; 
    double double2;
} DATA;

这个结构体的尺寸有多大?按道理讲:4+8+1+8=21字节。但是答案是24字节。

这是因为根据硬件的特征,对于不同数据类型的可配置地址收到一定限制。此时,编译器会适当的进行边界调整(布局对齐),在结构体内插入合适的填充物。

字节排序

sizeof(int)为4,在这4个字节中,整数究竟是以怎样的形式存放着呢?看下面这个程序:

#include<stdio.h>

int main(void) {
    int a = 0x12345678;
    unsigned char *a_p = (unsigned char*)&a;

    printf("%x\n", a_p[0]);
    printf("%x\n", a_p[1]);
    printf("%x\n", a_p[2]);
    printf("%x\n", a_p[3]);

    return 0;
}

运行结果:

PS C:\Users\Administrator\Desktop> .\Tout.exe
78
56
34
12

在这里我们使用a_p[0]至a_p[3]以字节为单位引用a的内容。

这种颠倒过来存放的配置方式称为小端字节序。顺序的配置方式称为大端字节序。大端和小端这样的字节排列方式称为字节排序。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值