The code is placed in GDB and Make.
-
函数声明与定义分离,声明写在头文件里,实现写在源文件里。 变量声明后在第一时间初始化,避免出错。
-
不要写
void main()
,在linux系统中,一个 C 语言程序的 main 返回值关系到一个系统是否能正常,高效的运行, 其中 0 在 Linux 程序管道通信间代表着无错可行的意思。
也不要省略 int 写main()
,虽然 C 语言缺省认为不显式声明返回值的函数返回值为 int , 但是这一步受编译器影响,有不确定因素。
不要写int main()
要写int main(int argc, char* argv[])
和int main(void)
, 这样才能避免本来应该是 void 的情况传入了参数无法报错。 -
不要把 C 语言代码当作 C++ 代码来编写,也不要把 C 的代码放在 cpp 文件中, 如下代码不能在 C++ 编译器下编译运行,因为 C++ 不支持
void*
指针隐式转换为其他类型的指针。/*file: test.c*/ #include <stdio.h> #define SIZES 5 int main(void) { int* c_pointer = malloc(SIZES * sizeof(int)); /*发生了一些事情*/ free(c_pointer); return 0; }
-
命名
- 函数命名
- 前缀
- set可以表示设置某个参数为某值;
- get可以表示获取某个参数的值;
- is可以表示询问是否是这种情况;
- 后缀
- max/min 可以表示某种操作的最大(小)次数;
- cnt 可以表示当前操作次数;
- key 某种关键值;
- 命名要简洁,具体操作可以留给文档说明;
- 常用词组:(不讲)
add / remove begin / end create / destroy insert /delete first / last get / release increment /decrement put / get add / delete lock / unlock open / close min / max old / new start / stop next /previous source / target show / hide send /receive source / destination cut / paste up / down
- 前缀
- 变量命名
- 所有字符使用小写;
- 含义多的用下划线 _ 辅助分隔单词;
- 以 = 为标准进行对齐;
- 类型,变量名各自左对齐;
- 少用全局变量,如果要用,考虑添加前缀
g_
#define
命名- 所有字符都用大写,并用 _ 分割单词
- 如果多于一个语句,使用 do{…}while (0)进行包裹,防止 ; 错误。
- enum 命名
- 所有字符都是用大写,并用 _ 分割单词;
- 与 define 相比, enum 适用于同一类型的变量声明,而不是单一独立的常量。往往出现都是成组。
- 函数命名
-
花括号
{}
- 当
{}
包裹的范围超过了一个屏幕时,可以适当使用注释来指明作用范围。 - 花括号使用风格统一:
例子:
/*Style 1*/ for (int temp = 0; temp < complex_int; ++temp) { k = temp; x = k + complex_int; } /*Style 2*/ for (int temp = 0; temp < complex_int; ++temp){ k = temp; x = k + complex_int; }
while (1){ if (tmp == NULL){ break; } else if (fanny == 1){ ... 大概超过了一个屏幕的代码 } /*else if fanny*/ }/*end while*/
- 当
-
空行与空格
- 空行起着分隔程序段落的作用。空行得体将使程序的布局更加清晰。空行不会浪费内存,虽然打印含有空行的程序会多消耗一些纸张,但是值得。
- 定义变量后要空行。尽可能在定义变量的同时初始化该变量,即遵循就近原则。如果变量的引用和定义相隔比较远,那么变量的初始化就很容易被忘记。若引用了未被初始化的变量,就会导致程序出错。
- 每个函数定义结束之后都要加空行。
- 两个相对独立的程序块、变量说明之后必须要加空行。比如上面几行代码完成的是一个功能,下面几行代码完成的是另一个功能,那么它们中间就要加空行。这样看起来更清晰。
- 等号两边使用空格
int a = 100
; - 多个变量的声明定义,或者函数定义,函数使用时,空格分开变量:
int i, j, k, x; printf("a int = %d is k = %d x = %d\n", complex_int, k, x); void present(int arg_1, double arg_2);
- 关键字之后要留空格。像
const
、case
等关键字之后至少要留一个空格,否则无法辨析关键字。像if
、for
、while
等关键字之后应留一个空格再跟左括号(
,以突出关键字。 - 函数名之后不要留空格,应紧跟左括号(,以与关键字区别。
(
向后紧跟。而)
,
;
这三个向前紧跟。紧跟处不留空格。,
之后要留空格。如果 ; 不是一行的结束符号,其后要留空格。注释 // 后留空格。- 赋值运算符、关系运算符、算术运算符、逻辑运算符、位运算符等双目运算符的前后应当加空格。
注意,运算符%
是求余运算符,与 printf 中%d
的%
不同,所以%d
中的%
前后不用加空格。 - 单目运算符前后不加空格。
- 像数组符号
[]
、结构体成员运算符.
、指向结构体成员运算符->
,这类操作符前后不加空格。 - 对于表达式比较长的 for 语句和 if 语句,为了紧凑起见,可以适当地去掉一些空格。但 for 和 if 后面紧跟的空格不可以删,其后面的语句可以根据语句的长度适当地去掉一些空格。
- 空行起着分隔程序段落的作用。空行得体将使程序的布局更加清晰。空行不会浪费内存,虽然打印含有空行的程序会多消耗一些纸张,但是值得。
-
成对书写
成对的符号一定要成对书写,如()
、{}
。不要写完左括号然后写内容最后再补右括号,这样很容易漏掉右括号,尤其是写嵌套程序的时候。 -
缩进
缩进不要通过键盘上的 Tab 键实现的,统一用四个空格,因为不同的环境下 Tab 的空格数量可能不一样,导致无法对齐。缩进可以使程序更有层次感。原则是:如果地位相等,则不需要缩进;如果属于某一个代码的内部代码就需要缩进。 -
对齐
对齐主要是针对大括号{}
说的:{
和}
分别都要独占一行。互为一对的{
和}
要位于同一列,并且与引用它们的语句左对齐。(两种花括号风格选一种){}
之内的代码要向内缩进四个空格,且同一地位的要左对齐,地位不同的继续缩进。- 还有需要注意的是,很多编程软件是会“自动对齐”的, 此外编程软件还有“对齐、缩进修正”功能。就是按
Ctrl+A
全选,然后按Alt+F8
,这时程序中所有成对的大括号都会自动对齐,未缩进的也会自动缩进。
-
代码行
- 一行代码只做一件事情,如只定义一个变量,或只写一条语句。这样的代码容易阅读,并且便于写注释。
if
、else
、for
、while
、do
等语句自占一行,执行语句不得紧跟其后。此外,非常重要的一点是,不论执行语句有多少行,就算只有一行也要加{}
,并且遵循对齐的原则,这样可以防止书写失误。
-
注释
C 语言中一行注释一般采用// comment
,多行注释必须采用/* comment */
。注释通常用于重要的代码行或段落提示。在一般情况下,源程序有效注释量必须在 20% 以上。虽然注释有助于理解代码,但注意不可过多地使用注释。- 注释是对代码的“提示”,而不是文档。程序中的注释不可喧宾夺主,注释太多会让人眼花缭乱。
- 如果代码本来就是清楚的,则不必加注释。例如:
这个就是多余的注释。注释要解释代码的目的、功能和采用的方法,提供代码以外的信息,帮助读者理解代码,防止没必要的重复注释信息。
i++; //i加1
- 边写代码边注释,修改代码的同时要修改相应的注释,以保证注释与代码的一致性,不再有用的注释要删除。
- 当代码比较长,特别是有多重嵌套的时候,应当在段落的结束处加注释,这样便于阅读。
- 每一条宏定义的右边必须要有注释,说明其作用。
-
其他杂项
switch
最后一定要放一个default
case
中如果要定义新变量,那这个case
内部的语句整体要用{}
包裹起来- 不要将
_
作为宏的开头或结尾 - 函数尽可能短小简洁,最好不超过一个屏幕;多功能函数难以理解、测试、维护;特别是不要把关联不强的两件事情放到同一个函数里面,使得修改其中一个功能时可能影响另一个功能。
- 在多重循环中尽量将最忙的循环放在内层;
- 某个循环即使是空语句也最好用
{}
包裹,以免出错 - 不要让函数返回值直接作为条件语句的判断,
if (is_eod(file) == 0)
好于if (!is_eod(file))
- 赋值一步一步来,不要写
num = (add = add + thr) + 20;
也不要写num = i++ + ++i;
这样没有意义结果又无法确定的等式。 - 不要比较两个浮点数是否相等或不等 (要比较的话得用一个极小值epison限定范围),也不要将浮点数用于离散的计数(本应该用
int
的场合)。事实上如果先把一个整数转换为浮点数,然后用log函数试图去取出这个浮点数对应多少位,是可能会出问题的,与原来的整数的位并不一定一致 - 代码文件最后都留一个空行。因为有些C代码编译的时候,不留空行可能出现问题
- 对于有含义的数字(如某些数组的大小)不要使用纯数字,如:
int a[100]
。 这样再读源代码不知道它的意思,要用#define
给它一个名字。如#define SIZE 100 int a[SIZE]
注意GDB调试时,并不是直接把源代码缓存起来了,而是根据行号和符号表去读源代码。 所以如果在调试时源代码发生变化,list命令可能会受到影响。
break if i = 50
表示当i为50时停住。
ignore <bnum> <count>
表示忽略断点号为 bnum 的停止条件 count 次
commands [bnum]
...
end
用于在断点bnum处自动执行命令,如:
break func if i > 1
commands
printf "i is %d\n",i
continue
end
表示在进入 func
后,如果 i
大于 1
就自动打印出值并自动继续运行
set i = 49
改变 i 的值为 49
对于动态数组
p *array@len
对于静态数组
p array_static
display 用于在停住或者单步前进时自动显示内容,如:
display array_static[j]
undisplay
与 delete display
后面跟 display 号码可以删除自动显示。
如 delete display 1-3
表示删除前三个 display
- info display
- set print pretty on 显示结构体会比较漂亮,不演示了
set $foo1 = result
set $foo2 = &result
set result = -1
p $foo1
p *$foo2
jump <linespec>
用于跳转,乱序执行程序return
强制返回。如在func里面使用:return 110
call
强制调用函数并显示返回值。如在main里面使用call func(6)
。
- 程序因为内存泄漏等原因骤停时,报错core dump,core文件记录这一刻内存等的状态,通过查看可以了解内存错误的位置等重要信息
- 启用 core 文件记录
ulimit -c unlimited
- 进入 core 文件
gdb test_for_core core.XXXX
- 在里面不能 r , s , n ,但是可以进行 p , bt ,where,list 等单纯查看类的操作。也可查看当时内存中的变量。
https://blog.csdn.net/yygydjkthh/article/details/43318523?ref=myread
目标 : 依赖
[TAB]命令(在shell中运行的)
- 第一个目标是执行 make 的终极目标。
- 目标可以不是文件,如 clean ,但是为了避免相关目录下有名为 clean 的文件,
因此用
.PHONY
目标的特殊规则说明 clean 是虚拟目标 - 确定了 makefile 的情况下,shell 执行
make
,发生如下步骤:
首先是检查终极目标是否存在或者是否需要更新(其依赖时间戳是否新于目标);若存在且“最新”,则执行结束,否则把每一个依赖作为目标进行类似的检查,直到最底端或报错为止;此基础上若未报错(如最基本的依赖不存在之类错误),则反向依次重建或创建对应的依赖,直到实现终极目标。(其中任何一步报错,make过程都会终止) - 类似的,shell 执行
make xxx.yyy
其中xxx.yyy
为 Makefile 中的目标时, 就把xxx.yyy
当作类似过程的“终极目标”来执行。
- 允许一个目标多个依赖或者多对一、多对多的依赖,但是后两者缺乏易读性,不推荐
- 依赖列表太长可以用
\
换行,但是\
后面不能有空格 - 可以使用通配符
*
?
... - order-only 依赖:
在a不存在时,b,c参与规则执行;在a存在需要重建时,只有b参与规则执行,c被忽略; 如果一个依赖在
a : b | c ....
|
两边出现,就当作常规依赖处理。
- 实际是在 shell 中运行,可以使用通配符如:
-rm *.o
- 隐含规则
对于xxx.o
这类目标,在没有规则时会自动调用默认隐含规则cc -c -o xxx.o xxx.c
因此对于目标文件为 xxx.o
依赖为 xxx.c
的目标可以省略规则的命令行,留下依赖的头文件等特定的依赖
objects = main.o command.o
使用同 shell,用 $(objects)
这种格式调用。
#
后跟的为注释,如果结尾是\
,那下一行也是注释- 需要用
#
字符的时候用\#
来转义代替
默认的情况下, make 会在工作目录(执行 make 的目录)下按照文件名顺序寻找 makefile 文件读取并执行,查找的文件名顺序为 : GNUmakefile、 makefile、 Makefile。
通常应该使用 makefile 或者 Makefile 作为一个 makefile 的文件名 (我们推荐使用 Makefile,首字母大写而比较显著,一般在一个目录中和当前目录的一些重要文件 (README , Changelist等)靠近,在寻找时会比较容易的发现它)。而 GNUmakefile 是不推荐使用的文件名,这是因为以此命名的文件只有 GNU make 才可以识别,而其他版本的 make 程序只会在工作目录下寻找 makefile 和 Makefile 这两个文件。
如果 make 程序在工作目录下无法找到以上三个文件中的任何一个,它将不读取任何其他文件作为解析对象。 但是根据 make 隐含规则的特性,我们可以通过命令行指定一个目标, 如果当前目录下存在符合此目标的依赖文件,那么这个命令行所指定的目标将会被创建或者更新,参见注释。
当 makefile 文件的命名不是这三个任何一个时,需要通过 make 的 -f
或者 --file
选项来指定 make 读取的 makefile 文件。给 make 指定 makefile 文件的格式为:-f NAME
或者 --file=NAME
,它指定文件 NAME 作为执行 make 时读取的 makefile 文件。
也可以通过多个 -f
或者 --file
选项来指定多个需要读取的 makefile 文件,
多个 makefile 文件将会被按照指定的顺序进行链接并被 make 解析执行。当通过 -f
或者 --file
指定 make 读取 makefile 的文件时, make 就不再自动查找这三个标准命名的 makefile 文件。
当前目录下不存在以 GNUmakefile、makefile、Makefile 命名的任何文件时,
- 当前目录下存在一个源文件
foo.c
的,我们可以使用make foo.o
来使用 make 的隐含规则自动生成foo.o
。当执行make foo.o
时,实际执行命令为:cc -c -o foo.o foo.c
- 如果当前目录下没有
foo.c
文件时,即make
对.o
文件目标的隐含规则中依赖文件不存在。 如果使用命令make foo.o
时,将回到如下提示:make : *** No rule to make target 'foo.o'. Stop.
- 如果直接使用命令 make 时,提示信息如下:
make : *** No targets specified and no makefile found. Stop.
参考 Linux 中国的 翻译规范