自己做动画网站,洛阳霞光做网站的公司,金牛区建设局网站,展示型网站首页设计解析gdb调试
gcc 源程序 -g#xff1b;加gdb调试信息gdb可执行程序#xff1b;#xff08;gdb调试#xff09;l#xff08;ist#xff09;#xff1a;查看源码#xff0c;按一下从main开始10行以此往后l n#xff1a;查看n处上下10行的源码run#xff1a;运行程序b…gdb调试
gcc 源程序 -g加gdb调试信息gdb可执行程序gdb调试list查看源码按一下从main开始10行以此往后l n查看n处上下10行的源码run运行程序break行号加断点info b查看当前断点delete 断点序号删除断点print 变量名查看变量的值continue程序继续运行单步运行程序next往后执行一步不进入函数内部step往下执行一步进入函数内部执行quit退出
变量的命名规则
程序中不得出现仅靠大小写区分的相似的标识符一个函数名禁止被使用于其他地方所有宏定义枚举常量只读变量全用大写字母命名用下划线分割考录到习惯性问题局部变量中可采用通用的命名方式但仅限于nij 等作为循环变量使用结构体被定义时必须有明确的结构体名定义变量的同时不忘初始化不同类型数据之间的运算要注意精度扩展的问题一般低精度向高精度数据扩展禁止使用八进制的常数0 除外因为严格意义上来讲 0 也是八进制数 和八进制的转义字符在计算机中任何以 0 开头的数字都被以为是八进制格式的数当然十六进制的0x不算。所以当我们写固定长度的数字时会存在一定的风险。如 code[3] 052;对应十进制的42
sizeof关键字 sizeof 在计算变量所占空间大小时括号可以省略而计算类型大小时不能省略。且一般情况下sizeof时在编译时求值所以 sizeofi不会引起副作用但是由于sizeofi和sizeofi的结果一样所以没有必要且不允许写这样的代码。同样“sizeofi1234”这样的代码也不允许**因为 i 的值没有被改变并没有被赋值为1234**。 sizeof操作符里面不要有其他运算符否则不会达到预期的目的。在C99 中计算柔性数组所占用空间大小时sizeof是在运行时求值此为特例。 ★在测字符串长度时strlen函数时计算字符串长度并不包含字符串在最后的’\0’。 if、else组合
bool变量与“零值”的比较 bool bTestFlag FALSE; (A) if (bTestFlag 0); (B) if (bTestFlag TRUE); (C) if (bTestFlag) (A) 容易产生歧义会误认为是整型变量。 (B) FALSE在编译器里被定义为 0 但 TRUE 的值则不唯一所以这种写法不妥 (C) 既不会引起误会也不会由于 TRUE 或 FALSE 的不同定义值而出错。 float变量与“零值”进行比较 float fTestVal 0.0 (A) if (fTestVal 0.0); (B) if ((fTestVal -EPSINON) (fTestVal EPSINON)); //EPSINON为事先定义好的精度 分析 float与double类型的数据都是有精度限制的不能直接拿来与0.0比 EPSINON为实现定义好的精度如果一个数落在[0.0-EPSINON, 0.0EPSINON]这个闭区间内我们认为在某个精度内它的值与零相等。扩展一下把0.0替换为你想比较的任何一个浮点数那我们就可以比较任意两个浮点数的大小了当然是在某个精度内 ★不要在很大的浮点数和很小的浮点数之间进行运算使用浮点数应遵循定义好的浮点数标准 五种类型的浮点异常是无效运算、被零除、上溢、下溢和不精确 四种射入方向向最接近的可表示的值、当有两个最接近的可表示的值时首选“偶数”值、向负无穷大向下和向正无穷大向上以及向0截断 指针变量与“零值”进行比较 int* p NULL; //定义指针一定要同时初始化指针和数组部分会详细讲 (A) if (p 0); (B) if (p); (C) if (NULL p); (A) 写法p时整型变量容易引起误会尽管NULL的值和0一样但意义不同 (B) 写法p时bool型变量容易引起误会不好 (C) 正确 else到底与哪个if配对 C语言中规定else始终与同一括号内最近的未匹配的if语句结合使用if语句的其他注意事项 规则1先处理正常情况再处理异常情况。 如果把执行概率更大的代码放到后面也就意味着if语句将进行多次无谓的比较。所以把正常情况的处理放在if后面而不要放在else后面 规则2确保if和else子句没有弄反 规则3赋值运算符不能使用再产生布尔值的表达式上。 任何被认为是具有布尔值的表达式上都不能使用赋值运算。 例如一下两种情况 if ((x y) ! 0) { foo(); } 或者 if (x y) { foo(); } 规则4所有if-else if 结构应该由else子句结束 不管何时一条fi语句后有一个或多个else if 语句都应该应用本规则最后的else if 必须跟有一条else语句。 switch、case组合
既然有了if、else组合为什么还需要switch、case组合
不要拿青龙偃月刀去削苹果 规则1每个case语句的结尾绝对不要忘了加break否则将导致多个分支重叠除非有使用多个分支重叠 规则2最后必须使用default分支。即使程序真的不需要default处理也应该保留以下语句 default break 这样做并非画蛇添足可以避免让人误以为你忘了default处理 规则3再switch case组合中禁止使用return语句。 规则4switch表达式不应是有效的布尔值。例如 switch (x 0) // not compliant - effectively Boolean { ... } case关键字后面的值有什么要求么 记住 case后面只能是整型或字符型的常量或常量表达式想想字符型数据在内存里是怎么存的。 case语句的排列顺序 有以下几种规则 1. 按字母或数字顺序排列各条case语句 2. 把正常情况放在前面而把异常情况放在后面 3. 按执行频率排列case语句 使用case语句的其他注意事项 有以下几种规则 1. 简化每种情况对应的操作 2. 不要为了使用case语句而刻意制造一个变量 3. 将default子句只用于检查真正的默认情况 break与continue的区别传送门 break关键字很重要表示终止本层循环。当代码执行到break时本层循环便终止。 而continue表示终止本次本轮循环当代码执行到continue时本轮循环终止进入下一轮循环 循环语句的注重点 建议使用以下规则 1. 在多层循环中如果有可能应当将最长的循环放在内层最短的循环放在外层以减少CPU跨切循环层的次数。 2. 建议for语句的循环控制变量的取值采用“半开半闭区间”的写法左闭右开。 3. 不能在for循环体内修改循环变量防止循环失控。 4. 循环要尽可能短要使代码清晰一目了然。 5. 把循环嵌套控制在3层以内。 6. for语句的控制表达式不能包含任何浮点类型的对象。 void关键字
void a void字面意思是“空类型”void * 则为“空类型指针”void * 可以指向任何类型的数据。void几乎只有“注释”和限制程序的作用因为从来没有人会定义一个void变量。 void真正发挥的作用在于对函数返回的限定对函数参数的限定 ✦任何类型的指针都可以直接赋值给void * 无需进行强制类型转换但反之则不能因为“空类型”可以包容“有类型”而“有类型”则不能包容“空类型”比如我们可以说“男人和女人都是人”但不能说“人是男人”或者“人是女人”。 void修饰函数返回值和参数 规则1如果函数没有返回值那么应该将其声明为void类型。 在C语言中凡不加返回值类型限定的函数就会被编译器作为返回整型值处理。如果函数没有返回值那么一定要声明为void类型。这既是程序良好可读性的需要也是编程规范性的要求。 规则2如果函数无参数那么应声明其参数为void 在C语言中可以给无参数的函数传送任意类型的参数但是在C编译器中编译同样的代码则会出错。在C中不能向无参数的函数传送任何参数。所以无论在C还是在C中若函数不接受任何参数则一定要指明参数为void。 void指针 规则1: 千万小心又小心地使用void指针类型。 按照ANSI标准不能对void指针进行算法操作之所以这样认定是因为它坚持进行算法操所的指针必须是确定知道其指向数据类型大小的也就是说必须知道内存目的地址的确切值。 在实际的程序设计中为符合ANSI 标准并提高程序的可移植性我们可以这样编写实现同样功能的代码 void* pvoid;
(char*)pvoid; // ANSI: 正确; GNU:正确
(char*)pvoid 1; // ANSI: 错误; GNU:正确 规则2如果函数的参数可以是任意类型指针那么应声明其参数为void* 典型的如内存操作函数memcpy和memset的函数原型分别为 void * memcpy(void * dest, const void * src, size_t len); void * memset(void * buffer, int c, size_t num); 有趣的是memcpy和memset函数返回的也是void * 类型。 void 不能代表一个真实的变量 void 不能代表一个真是的变量。void 体现了一种抽象这个世界上的变量都是“有类型”的譬如一个人不是男人就是女人人妖不算。 void 的出现只是为了一种抽象的需要如果你正确地理解了面向对象中“抽象基类”的概念也很容易理解 void 数据类型。正如不能给抽象基类定义一个实例我们不能定义一个 void让我们类比的称void为“抽象数据类型”变量。 return关键字
return 用来终止一个函数并返回其后面跟的值。 ✦注意return语句不可返回指向“栈内存”的“指针”因为该内存在函数体结束时自动销毁。
const关键字也许该被替换为readonly 传送门
const 是 constant 的缩写是恒定不变的意思也翻译为常量和常数等。很不幸正是因为这一点很多人都认为被 const 修饰的值是常量。这是不精确的精确来说应该是只读的变量其值在编译时不能被使用因为编译器在编译时不知道其存储的内容。或许当初这个关键字应该被替换为 readonly。 const 推出的初始目的正式为了取代预编译指令消除它的缺点同时继承它的优点。
节省空间避免不必要的内存分配同时提高效率 编译器通常不为普通 const 只读变量分配存储空间而是将它们保存在符号表中这使得它成为一个编译期间的值没有了存储与读内存的操作使得它的效率也很高。例如: #define M 3 // 宏常量
const int N 5; // 此时并未将N放入内存中
...
int i N; // 此时为N分配内存以后不再分配
int I M; // 预编译期间进行宏替换分配内存
int j N; // 没有内存分配
int J M; // 再进行宏替换又一次分配内存 const i 定义的只读变量从汇编的角度来看只是给出了对应的内存地址而不是像 #define 一样给出的时立即数所以const定义的 只读变量在程序运行过程中只有一份备份因为它是全局的只读变量存放在静态区而#define 定义的宏常量在内存中有若干个备份。#define 宏是在 预编译阶段进行替换而 const 修饰的只读变量实在编译的时候确定其值#define 宏 没有类型而 const 修饰的只读变量 具有特定的类型。 修饰指针 先忽略类型名编译器解析的时候也是忽略类型名我们看const 离哪个近”近水楼台先得月“离谁近就修饰谁 修饰函数的参数 const 修饰符也可以修饰函数的参数当不希望这个参数值在函数体内被意外改变时使用。 修饰函数的返回值 const 修饰符也可以修饰函数的返回值返回值不可被改变。 最易变的关键字——volatile
volatile int i 10; volatile 关键字告诉编译器i 时随时可能发生变化的每次使用它的时候必须从内存中取出 i 的值因此编译器生成的汇编码会重新从 i 的地址处读取数据。 这样看来如果 i 是一个寄存器变量表示一个端口数据或者是多个线程的共享数据那么就容易出错所以说 volatile 可以保证对特殊地址的稳定访问。 最会戴帽子的关键字——extern
博客园中有一篇关于extern的讲解传送门
struct关键字
struct是个神奇的关键字它将一些相关联的数据打包成一个整体方便使用。
在网络协议通信控制嵌入式系统驱动开发等地方我们经常要传送的不是简单的字节流char型数组而是多种数据组合起来的一个整体其表现形式是一个结构体。经验不足的开发人员往往将所有需要传送的内容依顺序保存在char型数组中通过指针偏移的方法传送网络报文等信息。这样做编程复杂易出错而且一旦控制方式和通信协议有所变化程序就要进行非常机制的修改非常容易出错。
这个时候只需要一个结构体就能搞定。平时我们要求函数的参数尽量不多于4个如果函数的参数多余4个使用起来非常容易出错包括每个参数的意义和顺序都容易出错效率也会降低与具体CPU有关ARM芯片对于超过4个参数的处理就有讲究具体请参考相关资料。这个时候可以用结构体压缩参数个数。
✦struct 与 class 的区别struct 的成员默认情况下的属性是 public而 class 成员的却是 private。
空结构体大小一般为1在GCC里面计算的值为0
union关键字
union 关键字的用法与 struct 的用法非常相似。
union 维护足够的空间来放置多个数据成员中的“一种”而不是为每一个数据成员配置空间。在 union 中所有的数据成员公用一个空间同一时间只能储存其中一个数据成员所有的数据成员具有相同的其实地址。
一个 union 只配置一个足够大的空间来容纳最大长度的数据成员在C里union 的成员默认属性为 public。union 主要用来压缩空间。如果一些数据不可能在同一时间同时被用到则可以使用 union。
柔性数组
这边引用博客园中的一篇关于柔性数组的文章传送门
大小端模式对union类型数据的影响
下面看一个例子
union
{int i;char a[2];
}* p, u;p u;
p-a[0] 0x39;
p-a[1] 0x38;
p.i 的值应该为多少呢 这里需要考虑存储模式大端模式和小端模式。
大端模式Big_endian:字数据的高字节存储在低地址中而字数据的低字节存放在高地址中。
小端模式Little_endian:字数据的高字节存储在高地址中而字数据的低字节则存放在低地址中。
union 型数据所占的空间等于其最大的成员所占的空间。对 union 型成员的存取都从相对于该联合体基地址的偏移量为 0 处开始也就是联合体的访问不论对哪个变量的存取都是从 union 的首地址位置开始。如此一解释上面的问题是否已经有了答案呢
如何用程序确认当前系统的存储模式
上述问题似乎还比较简单那来个有计数含量的请写一个 C 函数若处理器是 Big_endian则返回 0 若是 Little_endian ,则返回 1。
先分析下按照上面关于大小端模式的定义假设 int 类型变量i被初始化为1. 以大端模式存储其内存布局如图1.3所示。 以小端模式存储其内存布局如图1.4所示。
变量 i 占 4 字节但只有1个字节的值为 1另外 3 个字节的值都为 0。如果取出低地址上的值为 0毫无疑问这是大端模式如果取出低地址上的值为1毫无疑问这是小端模式。既然如此我们完全可以利用 union 类型数据“所有成员的起始地址一致”的特点编写程序。到现在应该知道怎么写了吧参考文案如下
int checkSystem()
{union check{int i;char ch;}c;c.i 1;return (c.ch 1);
}
现在你可以用这个函数来测试当前系统的存储模式当然你也可以不用功函数而直接去查看内存来确定当前系统的存储模式如图1.5所示
图1.5中 0x01 的值存在低地址上说明当前系统为小端模式。 不过要说明的一点是某些系统可能同时支持这两种存储模式你可以用硬件跳线或在编译器的选项中设置其存储模式。 留一个问题 在 x86 系统下以下程序输出的值为多少
#include stdio.hint main()
{int a[5] {1,2,3,4,5};int *ptr1 (int *)(a1);int *ptr2 (int *)((int)a1);printf(x, %x, ptr1[-1], *ptr2);return 0;
}
位域
对于位域的使用和自定义的行为需要详细说明且在使用前需要用代码check当前系统的模式大端或小端模式
enum关键字
枚举类型的使用方法 一般的定义方式如下
enum enum_type_name
{ ENUM_CONST_1,ENUM_CONST_2,...ENUM_CONST_n
}enum_variable_name; 注意enum_type_name是自定义的一种数据类型名而enum_variable_name为enum_type_name类型的一个变量也就是我们平时常说的枚举变量。实际上enum_type_name类型是对一个变量取值范围的限定而花括号内是它的取值范围即enum_type_name类型的变量enum_variable_name只能取值为花括号内的任何一个值都是常量一般用大写如果赋给该类型变量的值不在列表中则会报错或者警告。 enum变量类型还可以给其中的常量符号赋值如果不赋值则会从被赋初值的那个常量开始依次加一如果都没有赋值他们的值从0开始依次递增1. 枚举与#define宏的区别
#define宏常量是在 预编译阶段 进行简单替换枚举常量则是在 编译 的时候确定其值。一般在调试器里可以调试枚举常量但是不能调试宏常量。枚举可以以此定义大量相关的常量而#define宏一次只能定义一个。
留2个问题 1. 枚举能做的是#define宏能不能做到如果能那为什么还需要枚举 2. sizeof(ColorVal)的值是多少为什么
enum Color
{GREEN 1,RED,BLUE,GREEN_RED 10,GREEN_BLUE
}ColorVal;