版权声明:本文为 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
- 0x0015FCFB 是变量 c 的内存地址
- 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 列,如下图:
注意这 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 列,结果如下:
注意这连续的 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 个字节。
继续调试内存,输入指针地址,如下图:
继续调试内存,输入变量 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 指针分析下函数指针的内存,如下图:
可以看到 p1 指针的存储的内容为 fun 函数的地址,那 fun 函数在哪里呢?我们打开反汇编窗口:「调试 -> 窗口 -> 反汇编」,输入 fun 函数的地址 0X13811EF,定位如下位置:
可以看到一行汇编代码,看不懂也不要紧,知道这行代码是要跳转到地址 0x013813C0 即可,我们继续跳到这个地址:
是不是看到 fun 函数的汇编代码了,调试到此为止,不必完全理解所有的调试步骤,你只需要知道函数指针指向一个函数即可,演示这个调试步骤的「目的是」:为了让你直观的看到函数指针所指向的函数到底在哪里,实际调试出来给你看相信比口头说出来要更有说服力。
请一定要实践!
这次就分享这些吧,其实已经写了很多了,能耐心看下来的朋友相信对类型和指针一定有更加深刻的理解,刚入门 C 语言的同学相信也应该已经能够理解类型这个概念了,还不太理解的同学一定要动手调试这个过程,也许在你调试的时候就突然间恍然大悟,原来类型和指针这么简单啊!
ok,下次见。