美文网首页C语言
【C 陷阱与缺陷 学习笔记】(一)词法陷阱

【C 陷阱与缺陷 学习笔记】(一)词法陷阱

作者: 不会编程的程序圆 | 来源:发表于2020-06-05 10:53 被阅读0次

    码字不易,对你有帮助 点赞/转发/关注 支持一下作者

    微信搜公众号:不会编程的程序圆

    看更多干货,获取第一时间更新

    代码,练习上传至:https://github.com/hairrrrr/C-CrashCourse

    一 内容

    0. =不同于==

    当程序员本意是作比较运算时,却可能无意中误写成了赋值运算。

    1.本意是检查 x 与 y 是否相等:

    if(x = y)
        break;
    

    实际上是将 y 的值赋值给了 x ,然后再检查该值是否为 0 。

    2.本意是跳过文件中的空白字符:

    while(c = '' || c == '\t' || c == '\n')
        c = getc(f);
    

    因为 ' '不等于 0 (' '的 ASCII 码值为 32),那么无论变量为何值,上述表达式求值的结果都为 1,因此循环将进行下去直到整个文件结束。

    C 编译器发现形如 x = y 的表达式出现在选择语句,循环语句的条件判断部分时,会给出警告。当确实需要对变量进行赋值时,为了避免警告,我们应该这样处理:

    if((x = y) != 0)
        foo();
    

    如果将赋值写成了比较,也会造成混淆:

    if((filedesc == open(argv[i], 0)) < 0)
        error();
    

    本例中,open 执行成功返回非零值,失败返回 -1。本意是将 open 函数的返回值存储在变量 filedesc 中,然后将其和 0 比较大小,判断 open 执行是否成功 。==运算符的结果只可能是 1 或 0,永远不会小于 0,所以 error() 将没有机会被调用。

    1. &|不同于&&||

    比较 i & ji && j ,只要 i 和 j 是 0 或 1 ,两个表达式的值是一样的(||| 同理。)。然而,一旦 i 和 j 的值为其他,两个表达式的值不会始终一致。

    另一个区别是操作数带有自增自减的运算:

    i & j++, j 始终会自增;但是 i && j++ 有时 j 不会自增。

    2. 词法分析中的“贪心法”

    当 C 的编译器读入一个字符/后跟着一个字符*时,那么编译器就必须做出判断:时将其作为两个符号对待,还是合起来作为一个符号对待。这类问题的规则:每个符号应该包含尽可能多的符号

    例如:a---b(a--) - b含义相同,而与a - (--b)含义不同。

    又如:下面的语句本意是 x 除以 p 指向的值然后将结果赋值给 y

    y = x/*p;
    

    但是,实际上 /*被编译器理解为一段注释的开始。

    将上面的语句重写如下:

    y = x / *p;
    

    或者:

    y = x/(*p);
    

    老版本的编译器允许使用=+来代表现在+=的含义,这种编译器会将:

    a=-1;
    

    理解为:

    a =- 1;
    

    即为:

    a = a - 1;
    

    因此,如果程序员的原意为:

    a = -1;
    

    那么结果会让其大吃一惊。

    再如:

    a=/*b;
    

    在老版本的编译器会将其当作:

    a =/ *b;
    

    3. 整型常量

    许多编译器会把 8 和 9 作为把八进制的数字处理,这种处理方式来源于八进制数的定义。例如:0195 的含义是1x8^2 + 9x8 + 5x8^0也就是 141(十进制)或 0215(八进制)。ANSI C 标准中禁止这种用法。

    4. 字符与字符串

    单引号引起的一个字符实际上代表一个整数。整数值对应于该字符在编译器采用的字符集中的序列值。因此,对于采用 ASCII 字符集的编译器而言,'a'的含义与 97 (十进制)严格一致。

    用双引号引起的字符串,代表的确实一个指向无名数组起始字符的指针。该数组被双引号之间的字符以及一个额外的二进制值为 0 的字符\0初始化。

    比如,下面的这个语句:

    printf("Hello World\n");
    

    等价于:

    char hello[] = {'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd', '\n', 0};
    printf(hello);
    

    整数型(一般为 16 或 32 位)的存储空间可以容纳多个字符(一般为 8 位),因此有的编译器允许在一个字符常量(以及字符串常量)中包含多个字符。也就是说:用'yes'代替"yes"不会被该编译器检测到。前者的含义大多数编译器理解为一个整数值,由'y','e','s'所代表的整数值按照特定编译器实现中的定义方式组合得到。

    二 练习

    练习 1

    某些 C 编译器允许嵌套注释。请写一个测试程序,要求:无论编译器是否允许嵌套注释,该程序都能正常通过编译,但是两种情况下程序执行结果不同。

    对于符号序列:

    /*/**/"*/"
    

    如果允许嵌套注释,上面的符号序列表示:一个单独的双引号",因为最后的注释符前出现的符号都会被当作注释的一部分。

    如果不允许嵌套注释,上面的符号就表示一个字符串:"*/"

    Doug Mcllroy 发现了下面这个令人拍案叫绝的解法:

    /*/*/0 */**/1
    

    这个解法主要利用了编译器作词发分析时的“贪心法”规则。

    如果编译器允许嵌套注释,则将上式解释为:

    /* /*/0 */ * */ 1
    

    上式的值为 1

    如果编译器不允许嵌套注释,则解释为:

    /* / */ 0 * /**/ 1
    

    也就是 0*1,值为 0

    练习 2

    a+++++b 的含义是什么?

    上式唯一有意义的解析方式就是:

    a++ + ++b
    

    可是,根据“贪心法”的规则,上式应该被解释为:

    a++ ++ + b
    

    等价于:

    (a++)++ + b;
    

    但是 a++的值不能作为左值,因此编译器不会接受 a++ 作为后面 ++ 运算的操作数。

    参考资料《C 缺陷与陷阱》


    以上就是本次的内容,感谢观看。

    如果文章有错误欢迎指正和补充,感谢!

    最后,如果你还有什么问题或者想知道到的,可以在评论区告诉我呦,我在后面的文章可以加上。

    最后,关注我,看更多干货!

    我是程序圆,我们下次再见。

    相关文章

      网友评论

        本文标题:【C 陷阱与缺陷 学习笔记】(一)词法陷阱

        本文链接:https://www.haomeiwen.com/subject/kscazhtx.html