C语言内存与指针

一、问题引入

我们先编写一个交换两个变量数值的程序,名为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、字符指针变量使用注意事项
当字符指针指向字符串时,除了可以被赋值之外,与包含字符串的字符数组没有什么区别。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容