- 博客(48)
- 资源 (1)
- 收藏
- 关注
原创 内存篇之栈溢出
“溢出”这个词很生动,水满则溢,前面说过栈就象一个容器,容器装满了,还要往里装东西,当然就会溢出了。 有两种不同情景都被称为栈溢出,一种是栈中的数据被越界覆盖,wiki中称这种情况为stack buffer overflow。一种常用的黑客攻击手段--栈溢出攻击,就是通过栈越界访问,用事先设计好的数据覆盖正常栈里的内容。比如把保存函数返回地址的栈内存用某段黑客代码的地址覆盖,函数结束时不
2013-12-24 17:36:53 15586 1
原创 内存篇之堆泄漏
“堆泄漏”即常说的内存泄漏,是嵌入式软件里的常见问题,会导致软件运行一段时间后内存耗尽。什么是”堆泄漏”? 内存分配和释放的操作是程序员根据需要动态随机发起,程序本身(或编译工具)无法自动判断某块已分配的内存什么时候不再被使用,必须由程序员自己手动调用free释放,以便为其他程序腾出空间。而一旦程序员忘记释放某块内存,它就不能回到可用内存,系统总的可分配内存就随之减少,这就是内存泄漏
2013-12-24 15:21:10 1748
原创 C潜规则篇之如何实现平台无关
或许大家都有这样类似经历:要在某平台上开发一个模块,很幸运找到了功能类似的参考代码,拿来修改却发现它是基于其它平台,底层接口完全不同,且全都嵌在代码里,要一个个改。面对一堆编译错误,用着麻烦,丢了可惜,真成了“鸡肋”。 C代码复用不象JAVA那么简单,一般都要经过移植才能在不同平台运行。但如果事先把平台相关部分集中在一起,形成平台隔离层,C程序也能做到代码基本平台无关。判定C代码级平台无
2013-12-13 16:06:53 2572
原创 指针篇之十三 函数指针精彩回调
回调函数定义 回调是通过函数参数传递到其它代码内的某一段可执行代码。或者说,凡是自己定义又主动传给其他模块调用的,都是回调。回调允许底层模块调用高层定义的子程序。 理解回调首先要明白什么是层次/模块,软件模块是广义概念,可以包括功能库(也称SDK)、C++对象、组件以及操作系统等,但所有这些最终基本都可以归结为函数实体库。因此下文中上层模块就是指库的调用方(caller
2013-12-13 13:03:47 1631
原创 关于嵌入式软件开发的一些思考
今天是第一次开始写技术博客, a dear dairy moment. 首先感谢这个论坛上那些已经进入计算机软件开发自由王国的前辈高手们,正是他们在网络上不求回报地留下一篇篇精辟文章,如一盏盏指路明灯,真正为后来者照亮前进的方向;同时也想鼓励那些仍然身处迷雾苦苦挣扎的摸索者,只要坚定信心不动摇,终有一天能够登堂入室,苦尽甘来,从此终身享受嵌入式软件开发这个超级好玩的游戏。不必担心,不要沮丧,虽然编
2013-12-07 23:03:04 3660 2
原创 补遗篇之volatile
C中volatile关键字在程序操作变量时,强制读写变量所在内存,以阻止编译器对某些特殊变量的错误优化。反过来,只有靠程序员用volatile过滤一些特殊情况后,编译器才能大胆优化。volatile作用可总结为:阻止三种情形下的两种编译器优化。两种编译器优化 a. 数据流分析优化:编译器分析程序中变量在哪里赋值、哪里使用、哪里失效,根据分析结果消除多余的变量读取和赋值步骤,如:
2013-12-29 14:36:03 826
原创 补遗篇之内联与内嵌
从没想过有人会把这两个毫不相关的概念混为一谈,可招聘时还真就碰到问内嵌答内联,问内联答内嵌的情况。上网一查,原来内嵌汇编也常被叫内联汇编,中文表述IT名词时真就这么乏力么?蹩脚的撞名一个接一个。 内联函数即inline函数,其作用是“建议”编译器展开函数,不是一定展开,除非设置强制内联(如gcc的__attribute__((always_inline)))。展开即把函数代码插入被调用位
2013-12-27 12:24:31 1374
原创 补遗篇之C字符串
C并没有字符串类型,C字符串是一个以null('\0')结尾的字符数组,用null标识字符串结束。如{'a','b','c','d','\0'},只有包含这个'\0’才算C字符串。注意null也好,'\0'也罢,都只是0的不同表达,如: char a[] = “ABCDEF”; printf(“a=%s\n”,a); a[2]=0; printf(“a=%s
2013-12-27 10:42:31 1254
原创 补遗篇之sizeof
sizeof是C的单目运算符,它给出操作数存储所需的字节数,其操作数可以是表达式或类型名。sizeof()是运算符,不是函数,没有函数调用开销。其结果是在编译阶段由编译器自动给出,相当于常数,不象函数那样在运行时才得到返回值。 sizeof常用来屏蔽同一类型变量在不同平台上占用不同空间的问题,能提高C程序的可移植性。不懂得利用sizeof就出现:int *p = malloc(800
2013-12-26 22:41:44 966
原创 补遗篇之单行道标志const
面试时,问及const的含义,很多人会答:"const表示常量",这可不是考英文翻译,const应该更近似"只读"而不是常量。const语法 对非指针变量,const无论放在类型前或后,都表示变量属性为只读,运行过程中不能也不会赋值修改。如const int a;和int const a;这两种方式const作用相同,都表示a是一个常整型数。 而const修饰指针时则有所不
2013-12-26 20:59:34 923
原创 内存篇之越界访问
由于C语言指针高度灵活和自由,导致内存操作中陷阱很多,比如之前的堆泄漏,栈溢出,野指针等,此外还有一种常见的“内存访问越界”错误。广义讲,栈溢出也属于内存越界,只是对同一问题站在不同角度的说法。下面看几个内存越界的例子: void test1() { char string[10]; char* str1 = "0123456789";
2013-12-25 19:03:56 3789
原创 内存篇之野指针
前面两文中内存错误释放以及错误访问指向栈的指针,从另外角度,其中一部分错误还可以归结为访问了野指针。野指针又称悬挂指针,代表那些指向不可用内存区域的指针。操作野指针,程序会发生难以预料的错误。形成野指针主要有以下原因:1)指针没有初始化。指针变量创建时不会自动指向null,其缺省值是随机的,比如: int *p; *p = 0; 这种代码可能导致死机或者非法操
2013-12-25 18:58:11 1234
原创 内存篇之堆的错误释放
在我开始写程序时因为担心某些分支下忘记释放内存导致泄漏,就想能不能保险点,多加几次释放,但很快发现堆内存不能重复释放,一些错误释放甚至会导致系统崩溃。这类错误可分几种情况:1)重复释放某指针指向的内存,多数由于调用了不同层的子函数重复释放同一内存,如: int* p = malloc(20); …… free (p); …… System_
2013-12-25 10:18:57 2593
原创 内存篇之指向栈的指针
下面程序运行有什么样的结果? char *GetString(void) { char array[6]; strcpy(array, “hello”); return array; } void main() { char *pstr = NULL; pstr = GetStr
2013-12-25 09:29:00 2929
原创 内存篇之静态与动态分配
经常听到诸如静态分配/动态分配以及静态库等类似表述,这些究竟是什么含义呢?老规矩,看看万能wiki上的定义:“The word static refers to things that happen at compile time and link time when the program is constructed--as opposed to load time or run ti
2013-12-24 13:13:45 1046
原创 内存篇之程序内存布局
程序内存布局是理解软件本质的基础要素。支持一个程序运行的所有内存大体可分为以下几部分,或者说程序运行需要系统为其提供如下几部分存储区域: 栈:由编译器自动分配释放,存放函数参数,局部变量等,特点为后进先出。 堆:程序员调用malloc/free进行内存动态分配和释放所操作的内存区域。 全局数据区(静态区):全局变量以及static变量存放的内存区,注意static变量
2013-12-24 12:50:25 996
原创 内存篇之堆与栈的绕口令
堆(heap)/栈(stack或call stack)是两块功能完全不同的系统内存区,堆内存是由malloc/free函数动态申请和回收,而栈则是编译器与启动代码或线程创建代码配合,约定用CPU某寄存器标识最新使用位置的一段内存(所以分系统栈与任务栈,见后文)。但不知是谁添乱,用“堆栈”这个词代表“栈”的含义,导致中文里“堆“、“栈”和“堆栈”混在一起,三者的关系就象绕口令,“堆为堆,栈是栈,堆栈
2013-12-23 21:54:52 1274
原创 补遗篇之命名空间污染
概念 C标准规定,除非用static限定,否则全局变量与函数都作用于全局(见补遗篇static),也就是说一个模块中定义的函数与全局变量可在所有其他模块中被调用。这导致C的符号命名没有层次,不同模块间名字相互冲突的概率很高。学术的表述就是:C命名空间易被污染。 比如,不同软件模块中定义了同名但不同实现的非static函数,由于它们都全局可见,编译器链接时无法正确区分和选择这些同
2013-12-17 14:24:31 3807 2
原创 C陷阱篇之常见手误
C的某些语法容易让人不小心触雷,比如从0开始的下标 很多高级语言中,定义n个元素的数组,下标范围是从1到n,但C特殊,n元素的C数组中没有下标为n的元素,只有从0到n-1的下标。所以使用C数组时不要犯这种错误:int i, a[10];for ( i = 1; i 时超出数组边界八进制or十进制常数 C编译器会把数字025当作八进制,等于十进制的21,因此在使用
2013-12-17 11:26:29 921
原创 C陷阱篇之复合表达式中的确定与不确定
操作数求值顺序不定 C只规定小部分运算符以已知、特定顺序对其操作数求值,其他运算符的求值顺序没有定义,有偶然性。如a ,C标准规定:先求a,如a成立,再求c并计算整个表达式的值。任何编译器都不会先算c再算a,即&&运算符的求值顺序在C标准中有明确规定,先左后右。但下到a粒度时,C编译器对a和b的分别求值就没有先后规定,各种可能性都有。 C里只规定了四个运算符(&& ||,和?
2013-12-16 15:45:56 1096
原创 C陷阱篇之语法正确语义错误的编译器局限
编译器功能只是语法检查,只要语法正确,那它就遵循一个原则:程序员总是对的。其实也只能这样,如果脑子里想着A,实现的却是B,而A/B语法上都成立,那编译器除了认为你正确,还能做什么呢?只能我们自己注意区分A/B相似且语法都成立的下列情况。代码布局与缩进的误导 计算机从不受代码语法和布局影响,而人却易受眼睛影响做出倾向性判断,这些判断有时是错误的。如: for (i=0; i
2013-12-16 13:25:52 1910 1
原创 C陷阱篇之运算符优先级
C语言运算符有不同优先级,标准里对这些优先级的规定基本符合人们的正常认知习惯,但其中还有个别容易混淆。其实也不需要死记硬背所有优先级,只要注意几个例外就可以了:“+-”与”>>”运算符 因为和>>某些情况下相当于乘/除2n,很多人总认为它们的优先级也等于乘除而高于加减,但实际上移位运算优先级比加减低。这跟惯常思维不一致,所以当移位加减一起用时一定要注意。比如有人把n*5写成“res=
2013-12-15 21:45:22 928
原创 C陷阱篇之移位运算
C语言移位运算有一些不确定性及误区,主要包含两个问题:右移运算的空出位用0还是符号位填充?换句话右移n位与除以2n是否等价? 无符号整数右移,左边空位会用0填充,所以无符号右移n位与除以2n等价。 有符号整数右移,用0还是符号位填充由编译器自行决定,C标准中未定义。当有符号数为正数时,符号位也为0,所以总是用0填充,右移n位与除以2n等价;当有符号数为负,右移后填充
2013-12-15 21:38:11 2547
原创 C陷阱篇之define的缺陷
#define存在一些先天不足。在以下情形下要格外小心:避免宏参数中的++与--操作。 带参数的宏定义形式上与函数很接近,程序员调用时有时会把它们视为等价。但实际define定义的伪函数相比真正函数有一些使用限制,比如: #define max(A,B)((A)>(B)?(A):(B)) //看上去是想得到两个数中较大的 void main() {
2013-12-15 19:13:57 2827
原创 C陷阱篇之enum默认长度
enum型用于定义常量集合,相比#define有一些优势,如:enum是一种数据类型,使用时会检查类型匹配;enum增加了范围约束,避免变量赋值和使用时超出定义范围。但enum也有一个隐含问题:enum变量占用的空间与编译器相关。 多数编译器默认enum型长度等于int型,很多人也把enum型变量等同于int,但C标准在这里留下了尾巴:“枚举型尺寸是能够容纳最大枚举子值的整数尺寸”,“枚
2013-12-15 15:05:59 22130 2
原创 C陷阱篇之char的默认符号
代码里char c;这样的定义很少有人会留意,可就是这么个简单的定义却有很大的隐患。所有C原始类型中,char比较特殊,其他如long, int, short都默认有符号,相当于signed long, signed int和signed short,只有“char”不确定。某些编译器默认char是有符号的signed char,有些又当作unsigned char处理。例如ARM编译器A
2013-12-15 12:18:56 7744 1
原创 补遗篇之typedef
typedef关键字主要用于为C的固有数据类型定义别名,固有类型包括内部类型(int,char等)和自定义类型(struct,enum等),注意:typedef并不创建新类型,只是为现有类型加同义别名。typedef与#define区分 有时#define和typedef有表面类似的功能,正因为如此,更有必要弄清两者的本质差别和适用场合。typedef是为类型定义新别名,而#defi
2013-12-14 23:12:52 988
原创 补遗篇之struct(enum/union)多种定义方式
结构体(struct)是各种变量组合形成的新数据类型,struct有多种定义形式,总结下来可分四种,初学者容易混淆。下面一并整理区分,其中要特别留意区分三个元素:结构标签名,结构变量名,结构别名。形式1: struct结构标签名 { 类型成员变量名; ...... }结构变量名;实例: struct tagPERSON
2013-12-14 11:51:05 1074
原创 C陷阱篇之有/无符号数混合时自动类型转换
先看下面代码: void main() { unsigned int i=8; if(i>=-1) printf("8>=-1\n"); else printf("8 } 输出结果是8,明明赋值为8的变量i,结果被程序判定比-1还小,这是怎么回事? 问题根源在于变量i定义中的unsigned。我们都知道
2013-12-14 11:00:41 1568 2
原创 C潜规则篇之API接口
C语言模块化编程的基础是:把所有要隐藏的数据放在C文件中,所有要暴露给用户的信息放在API头文件,作为库和上层应用的桥梁。所以API头文件中的函数声明、数据类型等会被库自身和调用者共享。这就要确保库和上层应用的编译环境对它们有一致的解析,下列可能导致解析不一致的元素要格外注意: 1)char、enum这两个元素会被编译选项改变属性,因此最好不要直接出现在API接口中,如果非要包含,要注意
2013-12-13 22:30:29 3166 1
原创 C潜规则篇之防止重定义
C程序编译时常出现类似xxx redefinition错误,除了模块间的命名冲突(命名污染及static),问题多数与头文件管理有关。大型C工程的头文件管理很麻烦:C源文件往往包含很多头文件,头文件又包含其他头文件,形成复杂的嵌套包含;C没有严格限定源文件和头文件的功能边界,二者都可以包含全局变量和函数等实体定义。这都可能导致类型或实体定义被重复包含和展开,使编译器抛出重定义错误。 解决
2013-12-13 18:34:13 10635
原创 C潜规则篇之代码书写
代码书写过程中一些不好的习惯,也可能导致语法错误,代码丢失等问题,使得编码和维护的效率低下。下面几点虽然都是一些很细节的地方,但细节决定命运,编程中从细节处完善,养成好的习惯,也可以长久受益。修改前手动备份 修改代码时经常碰到这种情况,对某模块做修改,改了一半发现原来构想错误,想退回原点,却不幸忘记刚才修改了哪些地方。辛苦大半天,回不到解放前。即使现在有代码管理系统,一些小规模的调试
2013-12-13 18:15:55 953
原创 补遗篇之static
static的作用是IT公司C语言笔试必考题,看似简单却少有人能完整回答。因为它不止一个作用,并且随修饰对象的不同而变化,这里我总结成“有限生命变无限,无限空间变有限”。 static有三类作用对象:函数、局部变量和全局变量。其中局部变量只在函数运行期间存活,即“有限生命”;而函数和全局变量的作用域是无限的,也就是“无限空间”。 1)“有限生命变无限”是指static可以延长局
2013-12-13 16:36:20 848
原创 C潜规则篇之保持句法简单
有些程序员总喜欢使用精简而紧凑的C语句,以为可以提高程序效率,或者单纯只为显示自己掌握高深的语法,如: return ((int)atou(str+((*str == ‘+’)?1:0))); 这种写法简直让人抓狂,难道写点能让人看懂的代码很羞耻么?“? :”运算符效果等同于if-else语句,完全能分解得更直观: if(*str == '+') st
2013-12-13 14:52:54 795
原创 C潜规则篇之代码规范
很多初学者写代码只关注语法正确,而不在乎代码书写的规范和风格,导致写出来的代码乱七八糟,不但别人连自己都不愿多看一眼。这种代码难以维护,难以重用,更无法修改,功能再好也没人敢用。因此良好的代码规范不是可有可无,它不仅起美化作用,更重要的是使代码易阅读,易维护,提高工作效率。那么怎样给代码美容,使其看起来更“职业”呢? 除了上篇符号命名法外,前辈们在长期编程实践中总结出很多不成文的规范,教
2013-12-13 13:40:00 1262
原创 C潜规则篇之符号命名
C语言中如果能从名字看出符号(变量函数等)的类型和含义,就能一定程度上提高代码可读性,为此前人也总结出很多技巧,如著名的匈牙利命名法。不过每个人的盲点和兴奋点不同,预防与激励方法也不可能相同,所以各种命名法都有争议。这里只提醒大家注意好命名的作用,有意识地总结适合自己的方法,从而提高工作效率。下面是个人习惯,供参考: 1)宏定义和枚举常数全部用大写字母命名,在复合词里用下划线隔开每个单词
2013-12-13 13:21:09 1307
原创 指针篇之十二 函数指针数组实现跳转表
函数跳转表是把函数指针和数组结合在一起的应用方式,它充分利用数组的下标索引和函数指针的自动跳转功能,实现用户输入和功能函数的自动关联。比如一个软件计算器,用户输入两个数(op1/op2)和一个操作代号(oper),代码根据约定的代号/操作映射关系,决定调用哪种运算。常规实现方式为: switch(oper) { case ADD: result=add(op1,op
2013-12-13 11:27:31 2402
原创 指针篇之十一 函数指针
程序包含指令段和数据段,各自占用存储空间,有相应地址。函数可看作一组代表指令的二进制数集合,因此也能用指针指向和访问。函数指针就是指向可执行代码段中的某函数入口地址的指针,通过它可以把函数当成普通数据那样存储访问甚至移动拷贝。 函数指针定义为:返回值类型(*指针变量名)(形参列表);如:int (*f)(int x);注意形似的int *f(int x)代表返回值为指针的普通函数。函
2013-12-13 11:13:29 1171
原创 指针篇之十 传递指针共享内存
受硬件工艺制约,CPU访问外部内存的速度远远慢于寄存器以及片上内存,常常成为程序性能的最大瓶颈。而指针作为内存间接访问的索引,可实现内存共享机制,代替内存间的大数据搬运和拷贝。打个粗俗的比方,小孩在家里随地大小便,大人每次都要在后面处理,这时真希望他会蹲马桶啊!第一步一定是告诉并让他记住马桶在哪里,否则一旦既成事实,臭臭落地,就只能“拷贝搬运”了。所以,马桶的位置就是指针!函数调用链上的多余中
2013-12-09 15:20:31 3484
原创 指针篇之九 顺藤摸瓜式的指针数据链接
大科学家阿基米德曾说给他一根杠杆,就可以撬动整个地球,只要这根杠杆够长,够结实。同样,C语言编程时,掌握一个起始指针,加上合适的数据结构,就可以串起程序所需的所有数据。使用时只要拿到这根数据链的手柄(即起始指针),就能遍历访问链上的所有信息,这是指针的另一个作用:数据连接。在这种应用场合指针近似另一个概念--"句柄"。 英文"Handle"在计算机软件中译为"句柄"。广义上,如果能通过一
2013-12-09 14:21:16 983
空空如也
TA创建的收藏夹 TA关注的收藏夹
TA关注的人