zoukankan      html  css  js  c++  java
  • <编程精粹:编写高质量C语言代码> 读书笔记

    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
    ");
            abort();
        }
        while (size-- > 0)
            *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
    ");
            abort();
        }
    #endif
        while (size-- > 0)
            *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-- > 0)
            *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-- > 0)
            *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 != 0);
        *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) + 1);
        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++) != '')
            NULL;
        return ptr;
    }

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

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

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

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

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

    if (n < 0)
        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 > 0) {
            *str++ = (u % 10) + '';
            u /= 10;
        }
        *str = '';
        reverse_string(start);
    }

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

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

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

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

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

  • 相关阅读:
    饿了么P7级前端工程师进入大厂的面试经验
    前端程序员面试的坑,简历写上这一条信息会被虐死!
    这次来分享前端的九条bug吧
    移动端开发必会出现的问题和解决方案
    创建一个dynamics 365 CRM online plugin (八)
    创建一个dynamics 365 CRM online plugin (七)
    创建一个dynamics 365 CRM online plugin (六)
    创建一个dynamics 365 CRM online plugin (五)
    使用User Primary Email作为GUID的问题
    怎样Debug Dynamics 365 CRM Plugin
  • 原文地址:https://www.cnblogs.com/wwwjieo0/p/3517549.html
Copyright © 2011-2022 走看看