美文网首页
C语言内存与指针

C语言内存与指针

作者: 大嘴蝸牛 | 来源:发表于2023-02-22 06:29 被阅读0次

一、问题引入

我们先编写一个交换两个变量数值的程序,名为change.c,代码如下:

#include <stdio.h>

void change(int a, int b){
    int tmp = a;
    a=b;
    b=tmp;
}
int main(){
    int anum = 2;
    int bnum = 5;
    change(anum, bnum);
    printf("number a's value is %d, number b's value is %d\n", anum, bnum);
    return 0;
}

逻辑很简单,对于a和b两个变量,我们先创建一个临时变量tmp,然后将a的值赋给tmp,然后把b的值赋给a,最后把原来a的值再赋给b。这在数学上是没错的,然后我们执行以下代码,看看结果:


image.png

我们可以看到它的结果并没有改变,这不科学!
为什么会出现这种情况?
首先我们要了解这些变量和他们的值,在内存中是如何存储的。

二、内存

1、地址总线

10年前使用32bit的操作系统,现在使用64bit的操作系统。这个32和64,就是计算机硬件地址总线的数量。
现在1个地址线只能标记2个内存地址,即0号和1号,2根地址线能标记4个内存地址,即00、01、10、11,64跟地址总线就可以标记264个内存地址。
1个内存地址有1Byte的空间,232个内存地址可以标记4G的内存,264可以给16EB的内存标记地址,在现实生活中还不存在这样大的内存。
二进制可以转换为16进制,将内存地址用16进制来表示即如下图所示,左边绿色的是内存地址,右边黑色的是每个内存单元,即1Byte:

图片.png

2、操作系统

我们编写的所有程序都是运行在操作系统上的,操作系统会给内存指定编号,除此之外还给内存做了一些规划。例如在64位操作系统中,OS认为用户只要使用前48位内存就可以了,48位之后的内存都由系统来使用。
操作系统的内存和用户的内存分开使用,就可以避免被用户的程序占用,防止卡住、死机等状态,变得更安全。

内存划分区域如下图所示:


图片.png

下方为低编号地址内存,上面为高编号地址内存。我们编写的程序在编译为编译码后,这些数据会存在代码段在执行过程中产生的变量、声明的常量等数据会存放在数据段
代码段和数据段已经在内存中规划好了,全都存在低编号的内存地址中。
自由可分配内存就是程序执行的时候可以使用的内存,理论上它越大电脑就越不卡。
栈内存通常是用来存储最开始执行的程序,比如C语言中的main函数。

三、代码解析

1、GDP工具

gcc 的调试工具 gdb(通过gdb -help查看选项)
编译的时候使用-g选项可以进入调试模式,例如gcc -g main.c -main.out
调试命令:
gdb main.out
l(就是艾露)(list 显示源代码,l 或者 enter 继续执行l 继续显示)
1 break 12(行号) 打断点
2 start 单步调试
3 n(next 下一行)
4 p a (print 打印变量a)
5 s (step 进入方法,n执行下一行)
6 bt 查看函数堆栈
7 f 切换函数堆栈(f 1 切换到1)
8 q 退出调试
9 p *a(int *a 时 p a 打印出的是a的内存地址,p *a打印的是这个地址里对应的值.P &a 显示a的内存地址空间P &functionname p + &函数名,显示函数程序在代码段的内存地址)

2、变量和指针

首先编写如下代码:

#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;
}

int main(){
    int a = 3;
    int b = 4;
    int *pa = &a;
    int *pb = &b;
    int *pglobal = &global;
    int (*pquadrate) (int a) = &quadrate;
    int s = quadrate(a);
    printf("The space of square is %d\n", s);
    return 0;
}

上述代码分别是计算长方形和正方形的代码。然后使用调试模式编译代码:


image.png
输入list查看源代码
输入start开始调试
使用n执行下一步

通过调试模式我们可以知道,程序在执行的时候,记录运行到了哪一行,记录当前运行的函数是什么,记录当前所有的变量值是什么,这些数据就存储在内存的“栈”中。

(1)变量的本质

查看变量所在内存地址编号

通过上图的方式可以查看到变量a在内存所在的地址。所以变量是什么?变量名只不过是一个代号,变量的本质就是内存。

现在再解释以下第一节的问题,变量a和b都是内存的代号,我们传入的都是内存代号,所以就把内存代号中的值给了change函数,而change函数中的a和b,是该函数内另外的两个内存代号,和main中的内存并无关系,所以change函数中的a和b交换了函数值并没有让main中的anum和bnum交换。
如果想让main函数中的anum和bnum对应的内存交换数值,就需要把main函数中的anum和bnum所对应的内存当作参数传入change函数,这样才能交换。词时change函数中的a和b所对应的就不是两个数值了,而是两个内存地址(即内存空间)。
方法就是在调用change函数的时候,传入&anum而不是anum,&bnum而不是bnum。

(2)指针的本质

查看变量pa和查看pa所在的内存地址

上图中,pa的值是变量a的内存地址,而pa本身的内存地址则是0xbffff69c。
所以指针的本质就是保存变量的内存地址。

我们使用这样的方式来定义一个指针:
Type *p;

我们说p是指向type类型的指针,type可以是任意类型,除了可以是char,short, int, long等基本类型外,还可以是指针类型,例如int *, int **, 或者更多级的指针,也可是是结构体,类或者函数等。于是,我们说:

int * 是指向int类型的指针;

int **,也即(int *) *,是指向int *类型的指针,也就是指向指针的指针;

int ***,也即(int *) ,是指向int类型的指针,也就是指向指针的指针的指针;

…我想你应该懂了

struct xxx *,是指向struct xxx类型的指针;

其实,说这么多,只是希望大家在看到指针的时候,不要被int **这样的东西吓到,就像前面说的,指针就是指向某种类型的指针,我们只看最后一个号,前面的只不过是type类型罢了。

细心一点的人应该发现了,在“什么是指针”这一小节当中,已经表明了:指针的长度跟CPU的位数相等****,大部分的CPU是32位的,因此我们说,指针的长度是32bit,也就是4个字节!注意:任意指针的长度都是4个字节,不管是什么指针!(当然64位机自己去测一下,应该是8个字节吧。。。)

于是:

Type *p;

sizeof(p)的值是4,Type可以是任意类型,char,int, long, struct, class, int **…

以后大家看到什么sizeof(char*), sizeof(int *),sizeof(xxx *),不要理会,统统写4,只要是指针,长度就是4个字节,绝对不要被type类型迷惑!

3、函数指针

我们把之前的代码修改一下:

image.png
重新编译为调试模式,再p一下(*pquadrate)看一下有什么效果:
image.png
我们可以得出以下结论:
1.int quadrate(int a);是一个函数 int (*pquadrate)(int a)=&quadrate;则是指向这个函数的指针!int s=(*pquadrate)(a);可以调用函数!
2.一个指针变量*q 不加*号:P q 取出自己地址中存储的值(一个地址)。 加*号:P *q 取出指向地址中存储的值。

四、数组与字符串

1、数组的本质

Array数组其实是一种指针常量,而p则是一种指针变量(所以理论上,数组也是指针,数组和指针有一定的通用性,又有一定的差别,指针可以表达数组,而数组不可以表达指针);
若p是指针类型,则p++就是指针地址一次增加4个字节,也被称作指针偏移,其运行效率比数组高;

为什么p+4;*p=101;p[4]=101;等价?p[4]=101;代表从初始位置(以四个字节为偏移量进行偏移到达某个位置,然后对这个位置进行初始化赋值,即把101赋给这个地址所代表的内存空间。p+4代表从初始位置(a的地址就始)以四个字节为一步,向前走4步,到达某个位置,然后*p=101,代表此时指针指向的地址(即走了四步后所在位置)并对这个地址所在的内存空间进行初始化,赋值为101。

数组名本质是一个数组开头的地址,可以把它赋值给指针变量

int array[n];
int *p=array;

但是数组不可以array+=2;,而指针可以p+=2;,因为指针是变量,array是指针常量。

2、数组的声明和赋值

我们声明一个int类型的数组:

int a[5];

它表示声明了一个占5个内存地址的指针常量a,a中的数值都是int类型。
赋值如下:

a[0] = 1;
a[1] = 3;
a[2] = 4;
a[3] = 5;
a[4] = 6;

3、字符串

(1)字符串表示形式

①用字符数组实现
char string[] = “I love China!”;
② 用字符指针实现
char *string = “I love China!”;

(2)字符指针变量与字符数组

char *cp;char str[20];
①str由若干元素组成,每个元素放一个字符;而cp中存放字符串首地址
char str[20]; str=“I love China!”; (错)
char *cp; cp=“I love China!”; (对)
③str是内存指针常量;cp是内存指针变量
④cp接受键入字符串时,必须先开辟存储空间

(3)字符串与数组的关系

①字符串用一维字符数组存放。
②字符数组具有一维数组的所有特点。
③数组名是指向数组首地址的地址常量。
④数组元素的引用方法可用指针法和下标法。
⑤数组名作函数参数是地址传递等

(4)字符串与数组区别

①存储格式:字符串有结束标志\000
②赋值方式与初始化不同
③输入输出方式:%s %c
例子:

char   str[]={“Hello!”};    //对
char   str[]=“Hello!”;    //对
char  str[]={‘H’,‘e’,‘l’,‘l’,‘o’,‘!’};    //对
char   *cp=“Hello”;     //对
int    a[]={1,2,3,4,5};    //对
int   *p={1,2,3,4,5};    //错
char   str[10],*cp;
int    a[10], *p;
str=“Hello”;    //错
cp=“Hello!”;    //对
a={1,2,3,4,5};    //错
p={1,2,3,4,5};    //错

4、字符指针变量使用注意事项
当字符指针指向字符串时,除了可以被赋值之外,与包含字符串的字符数组没有什么区别。

相关文章

  • 慕课网-Linux C语言指针与内存-学习笔记

    Linux C语言指针与内存 工具与原理 指针 数组 字符串 堆内存与栈内存 gdb内存调试工具。 C语言中指针的...

  • 闲聊c/c++: 谈内存(大/小端,高/低字节,高/低地址)

    c/c++和其他语言最大区别在于: 指针 指针就是内存地址,一旦明白了指针与内存地址相关的内容,那理解其他语言,就...

  • C语言指针与内存

    初始指针使用指针来进行交换值 include int global = 0;int rect(i...

  • 链表代码注意事项

    1、理解指针或引用的含义 比如C语言是指针,就是相当于JAVA的引用 2、警惕指针丢失和内存泄漏 C 语言,内存管...

  • 2-Linux C语言指针与内存-学习笔记

    Linux C语言指针与内存 前面我们对于: c语言的基本用法 makeFile文件的使用 main函数的详解 标...

  • 02-C语言的指针

    02-C语言的指针 目标 C语言指针释义 指针用法 指针与数组 指针与函数的参数 二级指针 函数指针 指针在C中很...

  • C语言05- 指针

    C语言05- 指针 13:指针 指针是C语言中的精华,也是C语言程序的重点和难点。 13.1:指针定义与使用 指针...

  • [C]C语言指针

    Blog C语言指针详解 C语言字符串操作 指针的作用 指针是程序数据在内存中的地址,指针变量是用来保存这些地址的...

  • 《深入理解计算机系统》——Ch2-MemoryⅡ

    这一章继续学习内存。如何把C语言的变量存到内存,C语言中的指针和数组在内存的存取方式。 1 C语言中的&和* 第一...

  • C++语法之引用类型

    c语言的强大,无疑归功于指针。c++脱胎于c语言,保留了指针。掌控了指针就等于掌控了内存,优秀的程序用可以凭此在如...

网友评论

      本文标题:C语言内存与指针

      本文链接:https://www.haomeiwen.com/subject/gzsgfrtx.html