从 0 开始学习 C 语言:详细分析常见「类型」在内存中的存储

版权声明:本文为 cdeveloper 原创文章,可以随意转载,但必须在明确位置注明出处!

这个系列的文章我会挑出我认为比较重要的 C 语言知识来分享给大家,尽量写的富有实践性,让你看完之后不会觉得枯燥无味,而是看完就有想动手调试的欲望。

今天给大家分享的是 C 语言中「类型」这个概念,写过程序的同学对类型这个东西都不陌生,不管是什么编程语言都离不开类型。一些高级语言只用一个关键字就定义了所有的类型,比如 JS 中的 var,而 C 语言则为常见的类型都定义的对应的关键字。

我们写程序,不管是面向过程还是面向对象,都要使用到类型。其实说白了,对类型的简单理解就是:「一个变量或者对象在内存中的步长,以及可以对该对象执行的操作」。

不知道你有没有听过「步长」这个概念,字面意思可以理解为人走一步的长度,腿长的人跨度大,小短腿跨度小。把步长这个概念应用于理解类型上面,我觉得还是非常不错的,一个 32 位机器上的 char 类型的字节数为 1 Byte,则可以理解为该类型在内存中的步长为 1,而 int 字节数为 4 Bytes,则可以理解为步长为 4。

如果你还不是太理解的话,看看下面具体的内存分析,目前把内存当成一个「字节数组」即可,这样理解类型其实非常简单。

1. char 类型内存分析

运行这个例子,打印出变量 c 的 ANSI 值和内存地址。

#include <stdio.h>

int main(void) {
  char c = 'A';
  printf("c ANSI = %d, c address = 0X%X", c, &c);
  // 这里断点调试
  getchar();
  return 0;
}

我的结果是:

c ANSI = 65, c address = 0X15FCFB

我们调试内存,输入 c 的内存地址 0X15FCFB,右击选择「显示 1 字节整数」,并只「显示 1 列」,结果如下图:

[图片上传失败...(image-2571bc-1527085048097)]

可以看到下面这一行:

0x0015FCFB  41  A
  1. 0x0015FCFB 是变量 c 的内存地址
  2. 0x41 = 65 = A = 0100 0001 B

这样在内存中就很直接的看到了变量 c 的存储,它的步长只占 1 个字节,后面的地址都不属于变量 c,并且 ANSI 值为 41,表示字符 A。

2. int 类型内存分析

int 占 4 个字节,你理解了上面的例子,举一反三后你应该已经知道了 int 在内存中的存储方式,如果还有点迷糊,继续看这个例子。

#include <stdio.h>

int main(void) {
  // 2147483647 = 0x 7F FF FF FF
  int i = 2147483647;
  printf("i address = 0X%X", &i);
  // 这里断点调试
  getchar();
  return 0;
}

我的结果如下:

i address = 0X3BFECC

继续调试内存,输入 i 的内存地址,仍然右击显示 1 字节整数,显示 1 列,如下图:

int

注意这 4 行:

0x003BFECC  ff  .
0x003BFECD  ff  .
0x003BFECE  ff  .
0x003BFECF  7f  .

如果你理解了 char 的存储,相信你应该知道每一行是什么意思,这里还是要注意为什么是反的,这还是跟计算机的字节序有关,还不理解字节序的朋友可以看我上一篇文章中对字节序概念的介绍。

这 4 行连起来就是 0X7FFFFF,即 2147483647 的 16 进制表示,至于为啥用这个数来举例,因为它很特殊,学习的时候最好记住它,这里就不多提了,以后再说,不是目前的重点。

int 是不是很简单,那来个稍微复杂点的,分析下数组吧。

3. 数组类型内存分析

看下面这个简单的数组例子:

#include <stdio.h>

int main(void) {
  char c[3] = { 'a', 'b', 'c' };
  printf("c address = 0X%X, c[0] address = 0X%X", &c, &c[0]);
  // 这里断点调试
  getchar();
  return 0;
}

我的结果如下:

c address = 0x3CFB94, c[0] address = 0x3CFB94

继续调试内存,显示 1 字节整数,显示 1 列,结果如下:

int

注意这连续的 3 行,是一个 char 数组类型:

0x003CFB94  61  a
0x003CFB95  62  b
0x003CFB96  63  c

可以看到 a,b,c 在内存中顺序存储,且数组首地址和第一个数组元素的首地址相同,但是它们俩又有本质的区别。因为它们的步长(类型)不同,如果都对它们「取地址后加 1」,那么结果将千差万别,这个留给你自己去思考和调试。可不要小看这个问题,C/C++ 面试题很常见的,很多初学者都搞不明白。

再来分析下很多同学头疼的「指针」!

4. 指针类型内存分析

首先要说明下指针的类型大小,在 32 位机器(项目)上指针是 4 个字节,64 位机器(项目)上指针是 8 个字节,这是为什么呢?

其实可以把指针简单的「看作」地址,但是指针严格意义上不是地址,因为指针是变量,而地址是常量。当你把指针看作地址后,你调试内存就会发现 32 位机器上的地址是 32 位 = 4 Bytes,而 64 位机器上的地址是 64 位 = 8 Bytes,因此对应的指针就是 4 B 或者 8 B 了。

如果看不太懂的话,直接看看这个指针例子,例子是 32 位的,我的电脑是 64 位:

#include <stdio.h>

int main(void) {
  // 2147483647 = 0x 7F FF FF FF
  int i = 2147483647;
  int *p = &i;
  printf("&p = 0X%X, p = &i = 0X%X, sizeof(p) = %d", &p, &i, sizeof(int *));
  // 这里断点调试
  getchar();
  return 0;
}

32 位配置下的运行结果如下:

&p = 0x46F860, p = &i = 0x46F86C, sizeof(p) = 4

要理解:「指针是一个变量,也有自己的内存地址」,这里 0x46F860 就是指针 p 的内存地址,这个指针是 4 字节大小,里面存储的内容是变量 i 的地址 0x46F86C,该地址也是 4 个字节。

继续调试内存,输入指针地址,如下图:

pointer

继续调试内存,输入变量 i 的地址,如下图:

于是就得出一张经典的指针模型图:

pointer_i

看到这里你应该能够理解指针了,建议还不理解的朋友一定要自己调试这个过程,多调试内存就能理解了。至于在 64 位下的指针分析,就留给你锻炼吧,把当前 VS 的项目属性配置成 x64 模式,然后重新按照上面的步骤调试即可,不要眼高手低,不理解指针的话一定要动手调试!

至于二级指针,其实和一级指针是相同的,只不过第一级指针存储的内容是第二级指针的地址,第二级指针存储的内容才是实际变量的内存地址,建议你自己理解一级指针后,写个二级指针的例子,然后按照上面的方法重新调试 32 位和 64 模式。

5. 函数指针内存分析

在平常开发的过程中个,函数指针可以说是非常常用了,作为函数的参数用来回调是函数指针的一个经典用法,但是能够使用这项技术的前提是要完全理解函数指针。函数指针简单来说就是:「一个指向函数的指针,该指针存储的内容是一个函数的首地址,对该指针解引用,加上小括号() 就可以调用所指向的函数

例如下面这个例子:

#include <stdio.h>

// 函数名其实是一个函数指针变量
void fun(void) {
  printf("I'm fun.\n");
}

// &fun = fun
int main(void) {
  void (*p1)(void) = fun;
  printf("&p1 = 0X%X, p1 = fun = 0X%X,&fun = 0X%X\n", &p1, fun, &fun);
  p1();

  void(*p2)(void) = &fun;
  printf("&p1 = 0X%X, p2 = fun = 0X%X,&fun = 0X%X\n", &p2, fun, &fun);
  (*p2)();

  // 这里断点调试
  getchar();
  return 0;
}

这是我的运行结果:

&p1 = 0X3BF9E0, p1 = fun = 0X13811EF,&fun = 0X13811EF
I'm fun
&p2 = 0X3BF9D4, p2 = fun = 0X13811EF,&fun = 0X13811EF
I'm fun

可以看到这两种使用函数指针的方法都能正常运行,并且指向的 fun 函数地址都相同。注意:C 语言设计者为了方便允许使用第一种不带 * 直接调用函数的方式。

同样,来以 p1 指针分析下函数指针的内存,如下图:

fun_p

可以看到 p1 指针的存储的内容为 fun 函数的地址,那 fun 函数在哪里呢?我们打开反汇编窗口:「调试 -> 窗口 -> 反汇编」,输入 fun 函数的地址 0X13811EF,定位如下位置:

fun_p

可以看到一行汇编代码,看不懂也不要紧,知道这行代码是要跳转到地址 0x013813C0 即可,我们继续跳到这个地址:

fun_p

是不是看到 fun 函数的汇编代码了,调试到此为止,不必完全理解所有的调试步骤,你只需要知道函数指针指向一个函数即可,演示这个调试步骤的「目的是」:为了让你直观的看到函数指针所指向的函数到底在哪里,实际调试出来给你看相信比口头说出来要更有说服力。

请一定要实践!

这次就分享这些吧,其实已经写了很多了,能耐心看下来的朋友相信对类型和指针一定有更加深刻的理解,刚入门 C 语言的同学相信也应该已经能够理解类型这个概念了,还不太理解的同学一定要动手调试这个过程,也许在你调试的时候就突然间恍然大悟,原来类型和指针这么简单啊!

ok,下次见。

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

推荐阅读更多精彩内容

  • 指针是C语言中广泛使用的一种数据类型。 运用指针编程是C语言最主要的风格之一。利用指针变量可以表示各种数据结构; ...
    朱森阅读 3,387评论 3 44
  • 玉石,顾名思义,就是可以称之为“玉”的石头,在人们的印象里,玉石应该是润泽透亮,看起来很有灵性的,而在国家的珠宝玉...
    宝微微阅读 238评论 0 0
  • 一個正常而健康的公司,大家人格上是平等的,但分工上要有所區別。 老闆的時間就應該是最值錢的,不應該拿來做雜事;所有...
    湘湘玩赚派阅读 346评论 9 4
  • 如果不是为了应付考试刷分数,那你就应该紧紧围绕“沟通、交流、理解文化”这个目标来重新设计学习方法。 很多时候不是学...
    BryantHe阅读 627评论 0 2
  • 真正的忘记,并非不再想起,而是偶尔想起,心中却不再有涟漪。
    WAITING_4652阅读 98评论 0 0