• C专家编程 第2章 这不是Bug,而是语言特性 2.4 少做之过


    /*少做之过的特性就是语言应该提供但未能提供的特性,
     *如标准参数处理以及把lint程序错误从编译器中分离出来
     */
    /*2.4.1 用户名中若有字母f,便不能收到邮件*/ 
    /*用户名的第二个字母是f,邮件确实无法发送到他们那里*/
    /*许多人对ANSI C采用argc、argv的约定向C程序传递参数感到惊奇,但事实就是如此。
     *UNIX的约定有有所提升,达到了一个标准的层次,但此时却成了这个邮件Bug的原因之一
     */ 
    /*分析方法像试探法*/ 
    //5.
    if (argv[argc - 1][0] == '-' || (argv[argc - 2][1] == 'f')) {
        readmail(argc, argv);
    } else {
        sendmail(argc, argv);

    /*能正确发送邮件*/ 
    mail -h -d -f -/usr/linden/mymailbox 
    /*读取邮件,而不是发送邮件*/ 
    mail effie Robert
    /*修改方案*/ 
    if (argv[argc - 1][0] == '-' || argv[argc - 2][0] == '-' && (argv[argc - 2][1] == 'f')) {
        readmail(argc, argv);
    } else {
        sendmail(argc, argv);

    /*许多操作系统(如VAX/VMS)能够在程序中区分运行时选项和其他参数(如文件名),
     *但UNIX却不能,ANSI C也不能 */
      
    //6.
    /*软件信条*/
    /*Shell参数解析*/ 
    /*不充分的参数解析问题出现在UNIX的许多地方。找出目录中的那些文件是
     *链接文件,你可能输入下面的命令
     */ 
    ls -l | grep ->
    /*缺少重定向的名字,->被shell翻译成重定向符*/ 
    ls -l | grep "->"
    /*grep先看到减号,然后把整个参数翻译成大于号的一种未知组合形式,然后退出
     *,要解决问题,必须放弃使用ls命令*/ 
    file -h * | grep link
    /*创建一个文件,文件名以连字符开头,然后去发现无法用rm命令把连字符去掉。
     *一种解决方法是给出文件的完整路径名,这样rm就不会把连字符当做选项开关
     *并依次翻译文件名*/
    /*有些C程序员采用了一种约定,即带“--”的参数表示“从这里开始,没有参数是
     *选项开关,即使它是以连字符开头”
     *一种更好的解决方法是把包袱扔给系统而不是用户,使用参数处理器把参数分成选项
     *开关和非选项开关两种。目前这种简单的argv机制由于使用得太广,因而不可能对它
     *作任何修改。 
     */ 

    /*2.4.2 空格---最后的领域*/
    /*这里的几个例子,空格从根本上改变了程序的意思或程序的有效性
     *“\”字符用于对一些字符进行“转义”,包括newline(这里指回车键)。
     *被转义的newline在逻辑上把下一行当做当前行的延续,它可用于连接
     *长字符串。如果在“\”和回车键之间不小心留上一两个空格就会出现问题, 
     *\ newling \newling 效果是不一样的。 
     *因为你是在寻找无形的东西(在应该是newline的地方出现了一个空格
     *,注意newline并不是一个有形的字符,所以“\”后面有没有空格
     *在实际代码中根本看不出来)。newline在典型情况下用于转义连续多行
     *的宏定义。转义newline的另一种用处是延续一个字符串常量,如下: 
     */ 
    //7.
    char a[] = "Hi! How are you? I am quite a \
    long string, folded onto 2 lines";
    /*这种多行字符串常量的问题被ANSI C通过引入相邻字符串常量自动连接的约定得以解决*/
     
    //8.
    /*如果所有的空格都弃之不用,也会陷入麻烦*/ 
    z = y+++x;
    //correct
    z = y++ + x;
    //error
    z = y+ ++x
    /*ANSI C规定了一种逐渐为人熟知的“maximal munch strategy”(最大一口策略)
     *这种策略表示,如果下一个标记有超过一种的解释方案,编译器将选取能组成最长
     *字符序列的方案。 
     */ 
    z = y+++++x;
    //唯一有效的编排方式是: 
    z = y++ + ++x;
    //它还是会出现编译错误。 

    ///*有两个指向int的指针并想对int数据进行除法运算时,代码如下
    // *除法运算符“/”与“*”操作符之间缺少空格。它们紧贴在一起,
    // *被编译器理解成注释的开始部分,并把它与下一个“*/”之间的所有
    // *代码都变成注释的内容。 
    // */ 
    //9.
    int ratio = *x / *y;
    //int ratio = *x/*y;

    /*打算结束注释时却由于意外未能结束*/ 
    int hashval = 0;
    /*PJW hash function from "Compilers: Principles, Techniques, and Tools"
     *by Aho, Sethi, and Ullman, Second Edition.(*/)
    while (cp < bound) {
        unsigned long overflow;
        hashval = (hashval << 4) + *cp++;
        if ((overflow = hashval & (((unsigned long)OxF) << 28)) != 0) {
            hashval ^= overflow | (overflow >> 24);
        }
        hashval %= ST_HASHSIZE; /*选择起始桶*/ 
        /* 搜索每个表,这次搜索名字。如果失败,保存该字符串 
         * 进入字符串的指针,然后返回它
         */
        for (hp = &st_ihash; ;hp = hp->st_hnext) {
            int probeval = hashval; /*下一个探测值*/ 
        } 
    }
    /*2.4.3 c++的另一种注释形式//
     *把该符号以后直至行末的内容均作为注释内容。
     */ 
    a //*
    //*/ b
    /*上面的代码在C语言中表示a/b,但在C++语言中表示a。C风格的注释在C++语言中依然有效*/ 

    /*2.4.4 编译器日期被破坏*/ 
    /* 将源文件的timestamp转换为表示当地格式日期的字符串
     * 调用stat()得到UNIX格式的源文件修正时间
     * 调用localtime()将其转换成tm结构
     * 最后调用strftime()函数,把tm结构转换成以当地日期 
     * 格式表示的ASCII字符串
     */
    /*症状就是表示日期的字符串被破坏*/ 
    #include
    #include

    char *localized_time(char *filename) {
        struct tm *tm_ptr;
        struct stat stat_block;
        char buffer[120];
        /*获得源文件的timestamp,格式为time_t*/
        stat(filename, &stat_block);
        /*把UNIX的time_t转换为tm结构,里面保存当地时间*/
        tm_ptr = localtime(&stat_block.st_mtime);
        /*把tm结构转换为以当地日期格式表示的字符串*/
        strftime(buffer, sizeof(buffer), "%a %b %e %T %Y", tm_ptr);
        return buffer;  //program takes place

    int main() {
        char *p = localized_time("test.txt");
        printf("date = %s\n", p);
        return 0;
    }
    /*buffer是一个自动分配内存的数组,是该函数的局部变量。当控制流离开
     *声明自动变量(即局部变量)的范围时,由于该变量已被销毁,谁也不知道
     *指针所指向的地址的内容是什么
     *在C语言中,自动变量是在堆栈中分配内存。当包含自动变量的函数或代码退出
     *时,它们所占用的内存便被回收,它们的内容肯定会被下一个所调用的函数覆盖
     *。这一切取决于堆栈中先前的自动变量位于何处,活动函数声明了什么变量,
     *以及写入了什么内容。原先的变量地址的内容既可能被立即覆盖,也可能稍后
     *才能覆盖,这就是日期破坏问题难以发现的原因。 
     */ 
    //1.返回一个指向字符串常量的指针
    char *func() {
        return "Only works for simple strings";
        /*只适用于简单的字符串*/ 

    /*如果字符串常量存储于只读内存区但以后需要改写它时,你也会有麻烦*/ 
    //2.使用全局声明的数组
    char *func() {
        ...
        my_global_array[i] = 
            ...
        return my_global_array;

    /*它的缺点在于任何人都有可能在任何时候修改这个全局数组,而且该函数的下一次调用
     *也会覆盖该数组的内容。
     */ 
    //3.使用静态数组
    char *func() {
        static char buffer[20];
        ...
        return buffer; 

    /*这就可以防止任何人修改这个数组。只有拥有指向该数组的指针的函数(通过参数传递给它)
     *才能修改这个静态数组。但是,该函数的下一次调用将覆盖这个数组的内容,所以调用者必须
     *在此之前使用或备份数组的内容。与全局数组一样,大型缓冲区如果闲置不用,是非常浪费
     *内存空间的。
     */ 
    //4.显式分配一些内存,保存返回的值。 
    char *func() {
        char *s = malloc(120);
        ...
        return s;

    /*这个方法具有静态数组的优点,而且在每次调用时都创建一个新的缓冲区,所以该函数以后的
     *调用不会覆盖以前的返回值。它适用于多线程的代码(在某一时刻具有一个以上的活动线程的
     *程序)。它的缺点在于程序员必须承担内存管理的责任。
     */ 
    //5.也许最好的解决方法就是要求调用者分配内存来保存函数的返回值。为了提高安全性,
    //调用者应该通知指定缓冲区的大小(就像标准库中fgets()函数所要求的那样)
    void func(char *result, int size) {
        ...
        strncpy(result, "That's be in the data segment, Bob", size);

    buffer = (char*)malloc(size);
    func(buffer, size);
    ...
    free(buffer);
    /*如果程序员可以在同一代码块同时进行malloc和free操作,内存管理是最为轻松的。
     *这个解决方案可以实现这一点
     */
      
    return local_array;
    /*return local_array
     *"function returns pointer to automatic"(函数返回一个指向自动变量的指针)。 
     */ 
    /*2.4.5 lint程序不应该被分离出来*/
    /*lint程序能够检测到问题,并向你发出警告
     *把编译器中所有的语义检查措施都分离出来。错误检查有一个单独的程序完成,这个
     *程序被称为lint。这样编译器可以做得更小、更快而且更简单。
     */
    /*小启发*/
    /*早用lint程序,勤用lint程序*/ 
    /*lint程序是软件的道德准则,当你做错事,他会告诉你那里不对。
     *应该始终使用lint程序,按照它的道德标准办事。
     */
    /*source base 源代码基础
     *the lint merge from hell(地狱般的lint考验)
     */
    /*几个真正严重的bug
     *实参的类型在函数和调用之间发生了转变
     *一个期望接受3个参数的函数实际上只传递它一个参数,该函数从堆栈中再
     *抓两个参数。
     *变量在设置(初始化或赋值)前使用
     */
    /*经验不断证明,把lint程序作为一个独立的工具通常意味着把lint程序束之高阁。*/ 

    /*2.5 轻松一下---有些特性确实就是Bug */
    /*当然,任何人都知道从相对论的角度讲,信息也是有质量的*/ 

  • 相关阅读:
    mysql备份与还原
    O(N)求组合数
    解决:文件拖拽后文件关闭或者闪退的问题!
    可提升Python编程的简单小技巧
    云数据库与Mysq连接超详细版+报错解决方案+团队使用
    javascript 赋值运算符、一元运算符、运算优先级详细解析与代码实例
    内存管理---分页机制
    华为欧拉系统安装
    正则表达式模块re
    R语言使用lm函数构建带交互项的多元回归模型、使用step函数构建逐步回归模型筛选预测变量的最佳子集(step regression)
  • 原文地址:https://blog.csdn.net/weixin_40186813/article/details/126071171