c语言:13、指针与内存

本文深入探讨了C语言中的内存管理,包括进制表示、操作系统内存管理、变量与指针的本质、函数栈的工作原理。通过实例展示了指针自加、内存地址的连续性以及字符数组的使用。同时,利用gdb调试工具分析了程序在内存中的运行状态,揭示了函数内存分配和变量内存大小的概念。
摘要由CSDN通过智能技术生成


下方代码调试均在centos64位系统下进行

1、计算机中数据表示方式

进制

计算机中使用2进制、8进制、16进制表示数据;
计算机用二进制处理数据,计算机计算结果显示给人看就用十进制,编程要表示二进制数据时,因为内容过长,因此使用十六进制

2、内存管理

计算机中不管插几块内存条都会被计算机当做一个整体来计算内存大小;

	32位系统即使插再多的内存也只能使用4G内存,因为32位硬件平台上cpu地址总线只有32位,也就是操作系统寻址空间只有32位。32位指的是给内存编号只能编到32个二进制位,因此最终只能操作4G的内存空间。详细的感兴趣的朋友可以自行搜索。
	64位的操作系统因为寻址空间有2^64,因此理论上足够我们目前的使用了。

操作系统的内存管理

内存由操作系统统一管理;除了能给内存做编号外,还可以给内存做一定的规划,比如在64位操作系统中,用户程序使用的内存只要有前面的48位就可以了。

操作系统将用户内存和操作系统内存进行了隔离,前48位由用户使用,称为用户内存,后面的由操作系统使用。

隔离开后,即使用户内存使用不合理爆满,也不会影响操作系统内存,还是可以使用操作系统关闭不合理的内存占用进程。使得内存更安全。

C语言的语法是不允许我们直接操作代码段内存的;

标准C语言语法中是不支持直接对某一个内存地址进行操作的,操作系统会认定为非法操作。因为有可能会操作到操作系统的内存或其他应用程序的内存,这是不合理的,只有操作系统分配给应用程序的内存才是合理的操作。

操作系统对内存的划分:
在这里插入图片描述

代码段的内存地址是从下往上分配,数据段也是,栈内存是从上往下分配的。
因此代码段和数据段中的变量内存地址是越先分配的内存地址越小,栈内存中的变量则是越先分配的地址越大。

3、变量和指针的本质

变量的本质:
变量名只是一个代号,变量的本质就是内存。
写程序的时候,计算机处理的全是二进制数据,程序运行时二进制数据加载到内存中CPU才能取出来。
CPU要从内存中取数据,那么CPU就必须要知道从哪个内存单元中取,而变量就是起到一个标识作用,告诉CPU要到什么地方去取数据,或者要把数据写到什么地方去。这就是变量的本质。

指针的本质:
指针保存的是内存地址,因此指针本质上就是地址。

内存连续的证明:
下方调试例子证明了内存连续:

#include <stdio.h>
int main()
{
    int a = 3;
    int b = 2;
    int array[3];
    array[0] = 1;
    array[1] = 10;
    array[2] = 100;
    int *p = &a;
    int i;
    for(i=0; i<6; i++){
        printf("*p=%d\n",*p);
        p++;
    }
    printf("------------------------------\n");
    p=&a;
    for(i=0; i<6; i++){
        printf("p[%d]=%d\n", i, p[i]);
    }
    return 0;
}

在这里插入图片描述

4、程序在内存中的debug

4.1、编写main.c

#include <stdio.h>
int global = 0;

int rect(int a, int b)
{
    static int count =0;
    count++;
    global++;
    int s=a*b;
    return s;
}

int quadrate(int a)
{
    static int count=0;
    count++;
    global++;
    int s=rect(a,a);
    return s;
}

4.2、编译代码

gcc -g main.c

4.3、gdb调试程序

在这里插入图片描述

分析

由上图可知,源代码被加载到内存(内存代码段中)之后,能够借助gdb等调试工具进行调试,所以程序在运行的过程中,程序除了在代码段中以外,下方信息记录在了内存中:

  1. 当前调用哪个函数
  2. 当前调用的函数运行到多少行,并且这个函数中有哪些变量,这些变量的值是什么

内存栈中分配了内存给当前运行的函数,且内存栈中把函数执行过程中的所有状态全部记录了下来(就像拍了一个照片一样),比如上图中就记录下了当前代码运行到了27行

内存栈中一个函数可以被多次调用,多次调用时,每次调用都是一个独立的栈。

4.4、函数的内存

在代码段中内存地址是由低地址向高地址转移的,先声明的函数地址小,后声明的函数地址大。
在这里插入图片描述

4.5、变量的内存大小

在这里插入图片描述

4.6、指针变量的内存大小

在这里插入图片描述

5、函数栈是怎么工作的

继续调试第4步中的代码

栈的特点:先进后出
栈内存中最先分配的栈地址最大
在这里插入图片描述

6、指针自加的调试

6.1、调试

#include <stdio.h>
int main()
{
    int a = 3;
    int b = 2;
    int array[3];
    array[0] = 1;
    array[1] = 10;
    array[2] = 100;
    int *p = &a[0];
    int i;
    for(i=0; i<6; i++){
        printf("*p=%d\n",*p);
        p++;
    }
    return 0;
}

在这里插入图片描述

#include <stdio.h>
int main()
{
    int a = 3;
    int b = 2;
    int array[3];
    array[0] = 1;
    array[1] = 10;
    array[2] = 100;
    int *p = &array[0];
    p += 3;
    *p = 101;
    /*
	p += 3; *p = 101;与p[4]=101;等价,指针也可以使用数组的方式来访问	
    */
    p = &array[0];
    int i;
    for(i=0; i<6; i++){
        printf("*p=%d\n",*p);
        p++;
    }
    return 0;
}

运行上方代码,结果如下
在这里插入图片描述

6.2 指针本质上也是一种数组

如下方两个指针偏移量的操作,指针可以使用数组的方式来操作,
可以理解为:从本质上指针类型是数组变量、数组数据类型像是数组常量;

int a = 2;
int *p = &a;
p += 3;
*p = 101
//上方两行代码与下方一行代码等价
p[4] = 101;

字符数组类型和指针类型也是可以混用的

#include <stdio.h>
int main()
{
    char str[] = "hello";
    char *str2 = "world";
    char str3[10];
    printf("input the value \n");
    //字符数组类型和指针类型也是可以混用的,下方str3是一个字符数组
    scanf("%s", str3); 
    printf("str is %s\n", str);
    printf("str2 is %s\n", str2);
    printf("str3 is %s\n", str3);
}

调试上方代码结果如下:
在这里插入图片描述

7、字符数组

#include <stdio.h>
int main()
{
    char str[] = "hello";
    char *str2 = "world";
    char str3[10];
    printf("input the value \n");
    scanf("%s", str3);
    printf("str is %s\n", str);
    printf("str2 is %s\n", str2);
    printf("str3 is %s\n", str3);
    return 0;
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值