• 最近遇到的几个纯C编程的陷阱


    首先是一个不容易看出来的语法上的陷阱

    经过调试得出的错误是对非socket的socket操作出错,sockfd在调试过程中发现是0,不是一个合理的文件描述符。

    仔细一看原来是括号忘记加了,该运算是先用socket返回一个文件描述符fd,然后将fd<0的结果赋给sockfd,正常创建的socket的文件描述符为正,所以fd<0的结果为0,C语言中0代表false,任何赋值运算都是true,所以也经常会出现写掉一个=导致条件表达式一直为true。比如if (ptr = NULL)。所以很多人推荐将右值写在等号左边来让编译器来检查这种低级错误,比如if (NULL == ptr)。

    这种写法是《Unix网络编程》上推荐的写法,但是由于该书时间比较久远,那时候用的还是C89标准,也就是所有变量必须先定义再使用。以前看过的很多书上VC6.0的MFC程序代码就是这样,循环变量也在函数开头定义int i, j;(不过VC6本身对for循环就是不符合标准的= =b)

    但是C99标准已经不必将所有变量在函数最开始就定义了,这种惯用法不一定值得大力提倡,毕竟括号多了容易看漏。

    修改后

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        perror("socket error");
        return 1;  
    }
    

    清晰易懂。不过如果sockfd早就定义了,或者作为类的成员变量,之前那种2行合并的写法更好。

    下一个问题,字符指针、字符数组的问题

    void printPermisson(struct stat* buf)
    {  
        char* str = "-rwxrwxrwx
    ";                                          
        int flag[9] = { S_IRUSR, S_IWUSR, S_IXUSR, 
            S_IRGRP, S_IWGRP, S_IXGRP, S_IROTH, S_IWOTH, S_IXOTH };
        if (S_ISDIR(buf->st_mode))
            str[0] = 'd';
        for (int i = 0; i < 9; i++) {
            if (!(buf->st_mode & flag[i]))
                str[i + 1] = '-';
        }
        write(STDOUT_FILENO, str, 11);
    }  
    

    首先是这么写出错,因为虽然是char*,但是这种字面型字符串(string literal)是分配在静态区的(注意,"-rwxrwxrwx "是在静态常量区,但是指针str是在栈上),这里的char*和const char*无异,不能修改。

    OK,于是改成了指针数组char str[12]

    当然,这里我最初是写成返回字符串char*的,printf的工作留给函数使用者,即

    char str[12] = "-rwxrwxrwx
    ";
    // ... 处理
    return str;
    

    这是很严重的错误,指针str和它指向的字符数组内容"-rwxrwxrwx "都是在栈上,也就是说出了作用域后就会释放内存,返回的指针指向一片不可用的内存!

    所以这里只有char* str = (char*)malloc(12);这么动态分配内存,字符数组内容是在堆上,需要用户手动释放。

    然而,并不是“只有”这种手段,见我这篇博客最后的错误更正Linux和Windows的遍历目录下所有文件的方法对比

    除了动态分配外,也可以把字符串内容分配到静态区(不是之前string literal分配的静态常量区),它的声明周期和全局变量一样,而不是出了作用域就销毁,记得以前刚上C语言课时考试必考的一题就是这种,求func()函数运行几次后的输出

    void func()
    {
        int a = 0;
        static int b = 0;
        auto int c = 0;
        a++;
        b++;
        c++;
        printf("a=%d,b=%d,c=%d
    ", a, b, c);
    }
    

    注意,这里的auto是C标准的关键词,一个毫无卵用(仅仅是为了和static区分)的关键字,因为变量默认就是auto的,auto变量生存周期是作用域内(即{}内部

    ),而static变量则是一开始就存在,直到程序结束。

    PS:C++11开始auto已经变成非常好用也非常容易滥用的语法糖了,自动类型推导。跟这个auto完全不一样。

    回正题,于是呢,利用static变量生存周期的特性,把之前返回字符串char*的函数写成这样就没问题了

    static char str[12] = "-rwxrwxrwx
    ";
    // ... 处理
    return str;
    

    而readdir函数(见我之前提到的博客)返回的struct dirent*则是类似这样的手段来避免用户来释放内存(可惜也有像我这种自作聪明的人)

    最后就记录刚才调试了很久的错误,感觉C真是写得心累

    程序是读取Linux下的空洞文件(lseek或truncate等函数造成的,包含大量空白区域,即字符为,如果用普通的char buf[BUFFSIZE]来存储,很有可能中途就因为strlen(buf)为0导致退出,于是我的思路就是用lstat计算出文件大小size,用size / BUFFSIZE + 1作为循环次数。

    但是却输出了这样的东西。

    原因是我没有给buf初始化为0,然后每次读写的都是sizeof(buf)-1,为末尾留个结束符,但实际上字符数组未初始化,最后一位的字符是随机产生的(不为0)。

        char buf[LEN];
        printf("file size: %ld
    ", statbuf.st_size);
        for (int i = 0; i < statbuf.st_size / LEN; i++) {
            if (read(fd, buf, sizeof(buf) - 1) < 0)
                err_sys("read error");
            // ...
        }
    

    纠正方法就是char buf[LEN] = { 0 };

    唉,暂时说这么多了,基本功还是很不扎实,当然也有C++写习惯了的原因(构造/析构自动完成),实际写代码出现这些问题却不能很快速地发现。

  • 相关阅读:
    C++窗体应用程序
    C++继承方式简介及公有继承
    C++(继承的基本概念和语法)
    python(12)---科赫特雪花
    python(11)---pyinstaller
    20200914 day9 数据结构复习(一)
    20200914 day9 刷题记录
    20200913 day8模拟(二)
    20200912 day7 刷题记录
    20200912 day7 图论复习(一)
  • 原文地址:https://www.cnblogs.com/Harley-Quinn/p/6446295.html
Copyright © 2020-2023  润新知