0.规则
<The Elements of Programming Style>
<The Elements of Style>

1.假想的编译程序
(1)使用编译器提供的所有的可选警告设施

增强类型静态检查的能力
eg: void* memchr(const void* str, int ch, int size);
那个调用该函数时,即使互换其字符ch和大小size参数,编译器也不会发出警告

但是如果在函数原型中使用更加精确的类型,就可以增强原型提供的错误检查能力
void* memchr(const char* str, unsigned char ch, size_t size);

注:引入无符号数可以增强类型检查能力,但是也导致 无符号数带来的隐式转换错误(有符号数必须转为无符号数)

(2)使用lint等静态检查工具来检查编译器漏掉的错误
(3)如果有单元测试,就进行单元测试

2.自己设计并使用断言

/* memcpy v1: 拷贝不重叠的内存块 */
void* memcpy(void* to, const void* from, size_t size)
{
void* pto = (char*)to;
const void* pfrom = (const char*)from;
if (pto == NULL || pfrom == NULL) {
fprintf(stderr, "Bad args in memcpy\n");
abort();
}
while (size-- > )
*pto++ = *pfrom++;
return pto;
}
/* memcpy v2: 拷贝不重叠的内存块 */
void* memcpy(void* to, const void* from, size_t size)
{
void* pto = (char*)to;
const void* pfrom = (const char*)from; #ifdef DEBUG
if (pto == NULL || pfrom == NULL) {
fprintf(stderr, "Bad args in memcpy\n");
abort();
}
#endif
while (size-- > )
*pto++ = *pfrom++;
return pto;
}

既要维护程序的 release 版本,又要维护程序的 debug 版本
利用#ifdef DEBUG 调试宏这种方法的关键是保证调试代码不会在最终产品中出现

/* memcpy v3: 拷贝不重叠的内存块 */
void* memcpy(void* to, void* from, size_t size)
{
void* pto = (char*)to;
const void* pfrom = (const char*)from;
assert(pto != NULL && pfrom != NULL);
while (size-- > )
*pto+= = *pfrom++;
return pto;
}

尽可能多的使用断言,及早发现错误:
必须使用断言对函数的每个指针参数进行检查
必须立即使用断言对获取的资源(malloc获取的指针, fopen获取的FILE指针, 打开的数据库连接,获取的文件描述符等等)进行安全检查

/* memcpy v4: 拷贝不重叠的内存块 */
void* memcpy(void* to, const void* from, size_t size)
{
void* pto = (char*)to;
const void* pfrom = (const char*)from;
assert(pto != NULL && pfrom != NULL);
assert(pto >= pfrom + size || pfrom >= pto + size); /* 检查是否重叠 */
while (size-- > )
*pto++ = *pfrom++;
return pto;
}

在程序中使用断言检查语法中未定义行为特性的非法使用

3.为子系统设防
内存管理程序,可能犯的错误:
a.分配一个内存块并使用未经初始化的内容
b.释放一个内存块但继续引用其中的内容
c.调用realloc对一个内存块进行扩展,因此原来的内容发生了存储位置的变化,但程序引用的仍是原来存储位置的内容
d.分配一个内存块后立即"失去"了它,因为没有保存指向所分配内存块的指针
e.读写操作越过了所分配内存块的边界
f.没有对错误情况进行检查

/* new_memory v1 : 分配一个内存块 */
int new_memory(void** ptr, size_t size)
{
unsigned char** p = (unsigned char**)ptr;
*p = (unsigned char*)malloc(size);
return (*p != NULL);
}

然后如下调用:
if (new_memory(&block, 32))
成功,block指向所分配的内存块
else
不成功,block等于NULL

根据ANSI标准,调用malloc存在两处未定义行为,必须加以处理:
a.分配长度为0时,结果未定义
b.malloc分配成功,返回的内存块的内容未定义,可以是0,也可以是随意的信息

/* new_memory v2: 分配一个内存块 */
/* 加上内存块大小的检查和内存块的填充初始化 */
#define INIT_VALUE 0xA3
int new_memory(void** ptr, size_t size)
{
unsigned char** p = (unsigned char**)ptr;
assert(ptr != NULL && size != );
*p = (unsigned char*)malloc(size);
#ifdef DEBUG
{
if (*p != NULL)
memset(*p, INIT_VALUE, size);
}
#endif
return (*p != NULL);
}

在程序的调试版本中保存额外的信息,就可以提供更强的错误检查
只要相应的 release 版本能够满足要求,就可以在debug版本加入尽可能多的调试代码来检查错误

4.对程序进行逐条跟踪
5.糖果机界面

/* strdup: 为一个字符串建立副本 */
char* strdup(const char* str)
{
char* newstr = (char*)malloc(strlen(str) + );
assert(newstr);
strcpy(newstr, str);
retrun newstr;
}

不要把错误标志和有效数据混杂在一起返回

int resize_memory(void** ptr, size_t newsize)
{
unsigned char** p = (unsigned char**)ptr;
unsigned char* presize = (unsigned char*)realloc(*p, newsize);
if (presize != NULL)
*p = presize;
return (presize != NULL);
}

一个函数只干一件事,编写功能单一的函数,而不是多功能集一身的函数
反面教材: void* realloc(void** ptr, size_t size);
该函数改变先前已分配的内存块大小:
a.如果新请求大小小于原来长度,realloc释放该块尾部多余的内存空间,返回的ptr不变
b.如果新请求大小大于原来长度,扩大后的内存块有可能被分配到新地址处,该块的原有内容被拷贝到新的位置,返回的指针指向扩大后的内存首地址,并且新内存块扩大部分未经初始化
c.如果满足不了扩大内存块的请求,realloc返回NULL,当缩小内存块时,总是成功的
d.如果ptr == NULL 则realloc的作用相当于调用 malloc(size);
e.如果ptr != NULL 且 size == 0 则realloc的作用相当于调用 free(ptr);
f.如果ptr == NULL 且 size == 0 则realloc结果未定义

在允许大小为0的参数时要特别小心,一开始就要为函数的输入选择严格的定义,并最大限度地利用断言
为了程序的易读性和扩充性,不要使用布尔类型作为函数的参数类型

6.风险事业
ANSI并没有标准化 char,int,long 这样的基本数据类型
ANSI没有标准化 基本数据类型的原因:C语言产生于70年代,等到标准化时已经有了20多年写出来的代码基,定义严格的标准将会使大量现存代码无效

char* strcpy(char* pto, const char* pfrom)
{
char* ptr = pto;
while ((*pto ++ = *pfrom++) != '\0')
NULL;
return ptr;
}

上述代码在任何编译系统上都可以正确工作

int strcmp(const char* left, const char* right)
{
for (NULL; *left = *right; left++, right++) {
if (left == '\0')
return ;
}
return ((*left < *right) ? - : );
}

上述代码由于最后一行的比较操作而失去了可移植性。修改strcmp,只需声明 left 和 right 为 unsigned char 指针,或者直接在比较中先使用强制转型

(*(unsigned char*)left < *(unsigned char*)right)
for (unsigned char ch = ; ch <= UCHAR_MAX; ch++)
array[ch] = ch;

如果 ch = UCHAR_MAX 时,执行最后一次循环,循环之后,ch增加为 UCHAR_MAX + 1, 这将引起ch上溢为0,因此该循环变成了无限循环

if (n < )
n = -n;

这段代码可能会出现bug. 在二进制补码系统中,数据类型的表达范围不是对称的,例如 char [-128, 127) 如果n正好为最小负数,则 n = -n 则会上溢

7.编码中的假象
不要引用不属于你的未知存储区,"引用"意味着不仅读而且要写,这样可能会和别的进程产生不可思议的相互作用

/* unsign_to_str: 将无符号数转换为字符串 */
void unsign_to_str(unsigned u, char* str)
{
char* start = str;
while (u > ) {
*str++ = (u % ) + '\0';
u /= ;
}
*str = '\0';
reverse_string(start);
}

上述代码 是反向顺序导出数字,确正向顺序建立字符串,所以需要 reverse_string 来重排数字顺序

void unsign_to_str(unsigned u, char* str)
{
assert(u < UMAX);
/* 将每一位数字从后往前存储, 字符串足够大以便能存储 u 的最大可能值 */
char* ptr = &str[]; /* 假设 u <= 65536 */
*ptr = '\0';
while (u > ) {
*(--ptr) = (u % ) + '\0';
u /= ;
}
strcpy(str, ptr); }

函数能正确工作是不够的,还必须能够防范程序员产生明显的错误
尽量慎用静态(或全局)存储区传递数据

紧凑的C代码并不能保证得到高效的机器代码,首先应该考虑的是代码的正确性和可读性

8.剩下来的就是态度问题

<编程精粹:编写高质量C语言代码> 读书笔记的更多相关文章

  1. csapp读书笔记-并发编程

    这是基础,理解不能有偏差 如果线程/进程的逻辑控制流在时间上重叠,那么就是并发的.我们可以将并发看成是一种os内核用来运行多个应用程序的实例,但是并发不仅在内核,在应用程序中的角色也很重要. 在应用级 ...

  2. CSAPP 读书笔记 - 2.31练习题

    根据等式(2-14) 假如w = 4 数值范围在-8 ~ 7之间 2^w = 16 x = 5, y = 4的情况下面 x + y = 9 >=2 ^(w-1)  属于第一种情况 sum = x ...

  3. CSAPP读书笔记--第八章 异常控制流

    第八章 异常控制流 2017-11-14 概述 控制转移序列叫做控制流.目前为止,我们学过两种改变控制流的方式: 1)跳转和分支: 2)调用和返回. 但是上面的方法只能控制程序本身,发生以下系统状态的 ...

  4. CSAPP 并发编程读书笔记

    CSAPP 并发编程笔记 并发和并行 并发:Concurrency,只要时间上重叠就算并发,可以是单处理器交替处理 并行:Parallel,属于并发的一种特殊情况(真子集),多核/多 CPU 同时处理 ...

  5. 读书笔记汇总 - SQL必知必会(第4版)

    本系列记录并分享学习SQL的过程,主要内容为SQL的基础概念及练习过程. 书目信息 中文名:<SQL必知必会(第4版)> 英文名:<Sams Teach Yourself SQL i ...

  6. 读书笔记--SQL必知必会18--视图

    读书笔记--SQL必知必会18--视图 18.1 视图 视图是虚拟的表,只包含使用时动态检索数据的查询. 也就是说作为视图,它不包含任何列和数据,包含的是一个查询. 18.1.1 为什么使用视图 重用 ...

  7. 《C#本质论》读书笔记(18)多线程处理

    .NET Framework 4.0 看(本质论第3版) .NET Framework 4.5 看(本质论第4版) .NET 4.0为多线程引入了两组新API:TPL(Task Parallel Li ...

  8. C#温故知新:《C#图解教程》读书笔记系列

    一.此书到底何方神圣? 本书是广受赞誉C#图解教程的最新版本.作者在本书中创造了一种全新的可视化叙述方式,以图文并茂的形式.朴实简洁的文字,并辅之以大量表格和代码示例,全面.直观地阐述了C#语言的各种 ...

  9. C#刨根究底:《你必须知道的.NET》读书笔记系列

    一.此书到底何方神圣? <你必须知道的.NET>来自于微软MVP—王涛(网名:AnyTao,博客园大牛之一,其博客地址为:http://anytao.cnblogs.com/)的最新技术心 ...

  10. Web高级征程:《大型网站技术架构》读书笔记系列

    一.此书到底何方神圣? <大型网站技术架构:核心原理与案例分析>通过梳理大型网站技术发展历程,剖析大型网站技术架构模式,深入讲述大型互联网架构设计的核心原理,并通过一组典型网站技术架构设计 ...

随机推荐

  1. Nginx: ubuntu系统上如何判断是否安装了Nginx?

    问题描述:ubuntu系统上,如何查看是否安装了Nginx? 解决方法:输入命令行:ps -ef | grep nginx master process后面就是Nginx的安装目录. 延伸:1. 如何 ...

  2. matplotlib subplot 子图

    总括 MATLAB和pyplot有当前的图形(figure)和当前的轴(axes)的概念,所有的作图命令都是对当前的对象作用.可以通过gca()获得当前的axes(轴),通过gcf()获得当前的图形( ...

  3. VS/Qt C++和Matlab混合编程

    最近两天在搞C++和Matlab混合编程,这个中间过程真是让人心酸啊,最后还是搞定成功!现在把这个过程记录一下. 首先自己的电脑本来就安装着matlab2013b,按着网上的说法首先需要输入!mcc, ...

  4. 二叉搜索树详解(Java实现)

    1.二叉搜索树定义 二叉搜索树,是指一棵空树或者具有下列性质的二叉树: 若任意节点的左子树不空,则左子树上所有节点的值均小于它的根节点的值: 若任意节点的右子树不空,则右子树上所有节点的值均大于它的根 ...

  5. [LUOGU]P1508 Likecloud-吃、吃、吃

    题目背景 问世间,青春期为何物? 答曰:"甲亢,甲亢,再甲亢:挨饿,挨饿,再挨饿!" 题目描述 正处在某一特定时期之中的李大水牛由于消化系统比较发达,最近一直处在饥饿的状态中.某日 ...

  6. (69)zabbix监控惠普打印机

    假设公司有多个楼层或者分布在不同楼,打印机自然分布很广泛,打印机缺少油墨或者卡纸了,都需要员工找IT部门.我们使用zabbix对打印机进行监控,一旦缺少油墨,zabbix发出报警,it人员能够及时更换 ...

  7. Python爬虫系列-PyQuery详解

    强大又灵活的网页解析库.如果你觉得正则写起来太麻烦,如果你觉得BeautifulSoup语法太难记,如果你熟悉jQuery的语法,那么PyQuery就是你的最佳选择. 安装 pip3 install ...

  8. JS处理数据四舍五入,tofixed与round的区别

    此区别是在做微信端有关绑定设备数据曲线平滑处理的过程中,进行验证时候无意发现. 1 .tofixed方法 toFixed() 方法可把 Number 四舍五入为指定小数位数的数字.例如将数据Num保留 ...

  9. 实验二 JSP基本动态元素的使用

    实验二  JSP基本动态元素的使用 实验性质:验证性          实验学时:  2学时      实验地点: 一 .实验目的与要求 1.掌握JSP中声明变量.定义方法.java程序片及表达式的使 ...

  10. LeetCode(238) Product of Array Except Self

    题目 Given an array of n integers where n > 1, nums, return an array output such that output[i] is ...