解剖C语言
liuqiaoyu080512
这个作者很懒,什么都没留下…
展开
-
static变量 及 作用域控制
static变量 及 作用域控制 一、static变量 static变量放在函数中,就只有这个函数能访问它;放在函数外就只有这个文件能访问它。 下面我们看看两个函数中重名的static变量是怎么区别开来的(static.c):#include void func1(){ static int n = 1; n++;}void func2(){ s原创 2012-12-31 16:41:45 · 7172 阅读 · 1 评论 -
编译优化
编译优化 C语言没有汇编快,因为C语言要由编译器翻译为汇编,编译器毕竟是人造的,翻译出来的汇编源代码总有那么N条指令在更智能、更有创造性的我们看来是多余的。 C语言翻译后的汇编有如下恶劣行径:C语言偏爱内存。我们写的汇编一般偏爱寄存器,寄存器比内存要快很多倍。当然,寄存器的数量屈指可数,数据多了的话也必须用内存。内存多余读。假如在一个 for 循环中经常要执行 ++i 操原创 2012-12-31 16:41:33 · 2510 阅读 · 0 评论 -
变量名、函数名
变量名、函数名 C程序在执行的时候直接用内存地址去定位变量、函数,而不是根据名字去搜索,所以C程序执行的速度比脚本语言要快不少。 对于函数中的局部变量来说,编译为汇编的时候,名字就已经被彻彻底底地忘记了,因为局部变量在函数帧中,这一帧要占多少字节,各局部变量在帧中的相对位置,都在编译成汇编的时候就可以确定下来,生成目标文件、可执行文件的时候也不需要再更改。 而 全局变量、sta原创 2013-01-01 17:22:49 · 1737 阅读 · 0 评论 -
可变参数
可变参数 C语言的可变参数的实现非常巧妙:大师只用了 3 个宏就解决了这个难题。一、可变参数的应用 这里实现一个简单的可变参数函数 sum:它将个数不定的多个整型参数求和后返回,其第 1 个参数指明了要相加的数的个数(va.c):#include #include // 要相加的整数的个数为 nint sum(int n, ...){ va_list ap原创 2013-01-01 17:23:17 · 771 阅读 · 0 评论 -
函数指针
函数指针 一、函数指针的值 函数指针跟普通指针一样,存的也是一个内存地址,只是这个地址是一个函数的起始地址,下面这个程序打印出一个函数指针的值(func1.c):#include typedef int (*Func)(int);int Double(int a){ return (a + a);}int main(){ Func p = Doubl原创 2013-01-01 17:23:03 · 845 阅读 · 0 评论 -
C语言的栈是静态的
C语言的栈是静态的 C语言有了可变参数之后,我们可以传任意个数的参数了,似乎挺动态的了,但是可变参数函数还是不够动态。一、鞭长莫及 我们可以在 main 中写出好几条参数个数不同的调用 sum 的语句,但是具体到某一条语句,sum 的参数个数是一定的,比如上一篇中的 sum(2, 3, 4) 的参数个数是 3。如果程序运行中调用 sum 函数的时候,参数个数根据用户输入而定,那就原创 2013-01-01 17:23:31 · 947 阅读 · 0 评论 -
汇编实现的动态栈
汇编实现的动态栈 这一篇就是实现 d_printf,废话不多说,直接上代码。由于 VC 的内联汇编还是比较清晰,那就先贴 VC 版的。一、d_printf VC版#include void d_printf(const char *fmt, int n, int a[]){ static int size1, size2; static const char原创 2013-01-01 17:23:49 · 1492 阅读 · 4 评论 -
未初始化全局变量
未初始化全局变量 为下一篇介绍进程内存分布做准备,这一篇先来介绍一下未初始化全局变量: 未初始化全局变量,这名字就很直白,就是 C 程序中定义成全局作用域而又没有初始化的变量,我们知道这种变量在程序运行后是被自动初始化为 全0 的。编译器编译的时候会将这类变量收集起来集中放置到 .bss 段中,这个段只记录了段长,没有实际上的内容(全是0,没必要存储),在程序被装载时操作系统会为它分原创 2012-12-31 16:40:50 · 15580 阅读 · 6 评论 -
内联汇编
内联汇编 内联汇编是指在 C/C++ 代码中嵌入的汇编代码,与全部是汇编的汇编源文件不同,它们被嵌入到 C/C++ 的大环境中。一、gcc 内联汇编 gcc 内联汇编的格式如下:asm ( 汇编语句 : 输出操作数 // 非必需 : 输入操作数 // 非必需 : 其他被污染的寄存器 // 非必需 ); 我们通过一个简单的例原创 2013-01-01 17:23:41 · 20314 阅读 · 3 评论 -
内存对齐
内存对齐 为什么要进行内存对齐 在计算机组成原理中我们学到:一块内存芯片一般只提供 8 位数据线,要进行 16 位数据的读写可采用奇偶分体来组织管理多个芯片, 32 位也类似: 这样,连续的四个字节会分布在不同的芯片上,送入地址 0,我们可将第 0、1、2、3 四个字节一次性读出组成一个 32 位数,送入地址 4(每个芯片接收到的地址是1),可一次性读出 4、5、6、7 四个字原创 2012-12-30 16:47:29 · 2115 阅读 · 0 评论 -
进程内存分布
进程内存分布 之前一直在分析栈,栈这个东西的作用也介绍得差不多了,但是栈在哪儿还没有搞清楚,以及堆、代码、全局变量它们在哪儿,这都牵涉到进程的内存分布。linux 0.01 的进程内存分布 内存分布随着操作系统的更新换代,越来越科学合理,也越来越复杂,所以我们还是先了解一下早期操作系统的典型 linux 0.01 的进程的内存分布: linux 0.01 的一个进程固定拥有原创 2012-12-31 16:41:18 · 4979 阅读 · 3 评论 -
函数帧
函数帧 这标题一念出来我立刻想到了一个名人:白素贞……当然,此女与本文无关,下面进入正题:其实程序运行就好比一帧一帧地放电影,每一帧是一次函数调用,电影放完了,我们就看到结局了。 我们用一个递归求解阶乘的程序来看看这个放映过程(fac.c):#include int fac(int n){ if(n <= 1) return 1; ret原创 2012-12-30 16:52:46 · 2347 阅读 · 0 评论 -
照妖镜和火眼金睛
照妖镜和火眼金睛 如果在 linux 下编写 C 程序,那么你将获得两个犀利的法宝:照妖镜 一个C程序(max.c):#define MAX(a,b) ((a)>=(b)?(a):(b))int main(){ int c=MAX(1,2); // 注注注注释 return 0;} 程序很简单,就是定义和使用一个MAX宏,宏在正式编译前是会被替原创 2012-12-25 21:20:54 · 2362 阅读 · 0 评论 -
局部变量
局部变量 在接下来的几篇文章中,我将利用"火眼金睛"来分析一个个的C程序,为大家揭开 C 语言的奥秘。怪题 有一天,一个朋友发现一段奇怪的 C 程序:int i = 3;int ans = (++i)+(++i)+(++i); 书上说答案是 18,我还以为是 4+5+6=15 呢。验证 然后我就想着验证一下结果到底是怎样的,我写了如下的测试程序(inc.c):原创 2012-12-27 20:56:29 · 607 阅读 · 0 评论 -
全局变量
全局变量 实验品 小白鼠(global.c):#include int i = 1;int main(){ ++i; printf("%d\n",i); return 0;} i 的定义被放在了函数体的外边, i 就成为了全局变量,程序运行的结果我不关心,我关心的是 i 最后变成了什么。反汇编 这次悟空的火眼金睛也不给力了(后面我原创 2012-12-27 20:57:17 · 702 阅读 · 0 评论 -
函数调用
函数调用 前言 本来打算在写完 数组、结构体、指针 等之后再写函数调用的,因为函数调用会牵扯到这些东西,但是我觉得那样做的话总会露出点意犹未尽的马脚,所以还是先简单地分析一下函数调用吧,之后再不断的完善函数调用这个大家伙。C源程序(double.c)#include int Double(int b){ int c; c = b + b; ++b;原创 2012-12-28 22:10:55 · 870 阅读 · 0 评论 -
数组和指针
数组和指针 指针和数组有什么区别? C程序(array.c):#include int main(){ int date[3] = {2012,11,11}; int *p = date; int a = date[1]; int b = p[1]; printf("a:%d b:%d\n", a, b); printf原创 2012-12-29 16:19:04 · 535 阅读 · 0 评论 -
结构体
结构体 结构体是 C 语言主要的自定义类型方案,这篇就来认识一下结构体。一、结构体的形态 C源程序(struct.c):#include typedef struct{ unsigned short int a; unsigned short int b;}Data;int main(){ Data c, d; c.a = 1;原创 2012-12-29 16:20:46 · 667 阅读 · 0 评论 -
谁调用了main?
谁调用了main? 这是函数帧的应用之一。操作可行性 从上一篇中可以发现:用帧指针 ebp 可以回溯到所有的函数帧,那么 main 函数帧之上的函数帧自然也是可以的;而帧中 旧ebp 的上一个四字节存的是函数的返回地址,由这个地址我们可以判断出谁调用了这个函数。准备活动 下面就是这次黑客行动的主角(up.c):#include int main(){原创 2012-12-30 16:53:43 · 1686 阅读 · 0 评论 -
奇怪的宏
奇怪的宏 这一篇介绍这些奇怪的宏:一、do while(0) 为了交换两个整型变量的值,前面值传递中已经用包含指针参数的 swap 函数做到了,这次用宏来实现(swap.c):#include #define SWAP(a,b) \ do{ \ int t = a; \ a =原创 2012-12-29 16:21:04 · 629 阅读 · 0 评论 -
字符串
字符串 这一篇分析字符串,字符串经常被使用,但是它的秘密也不少:一、字符串的存储位置 C源程序(string1.c):#include int main(){ puts("Hello, World!"); return 0;} 我们直接看可执行文件的反汇编结果:[lqy@localhost temp]$ gcc -o string1 stri原创 2012-12-29 16:19:56 · 847 阅读 · 0 评论 -
所有递归都可以变循环
所有递归都可以变循环 这是函数帧的应用之二。 还记得大一的C程序设计课上讲到汉诺塔的时候老师说: 所有递归都可以用循环实现。这听起来好像可行,然后我就开始想怎么用循环来解决汉诺塔问题,我大概想了一个星期,最后终于选择了……放弃…… 当然,我不是来推翻标题的,随着学习的深入,以及"自觉修炼",现在我可以肯定地告诉大家:所有递归都可以用循环实现,更确切地说:所有递归都可以用循环+栈实现原创 2012-12-30 16:54:04 · 11609 阅读 · 5 评论 -
值传递
值传递 在函数调用那一篇里已经揭开了值传递的真相: 实参、形参有各自的存储空间(实参也可能只是一个值,而没有存储空间),实参 -> 形参是个值拷贝的过程,在函数调用前完成了这个拷贝过程,此后如果函数中对形参进行修改,实参的值不会跟着变。 但并不是我们就没办法在函数中修改外部变量了,用指针就好了,我们不需要修改指针的值,而只是修改指针指向的内存块:0重指针(基本类型、结构体)i原创 2012-12-28 22:11:14 · 508 阅读 · 0 评论