C语言在许多不同的系统平台上都有实现。的确,使用C语言编写程序的一个首要原因就是,C程序能够方便地在不同的编程环境中移植。
  不同的系统有不同的需求,因此我们应该能够预料到,机器不同则其上的C语言实现也由细微差别。今天,一个C程序员如果希望自己写的程序在另一个编程环境也能够工作,他就必须掌握许多这类细小的差别。本章要讨论的是关于可移植性的几个最常见的错误来源,重点放在语言的属性上,而不是在函数库的属性上。
 

7.1 标识符名称的限制

  某些C语言实现把一个标识符中出现的所有字符都作为有效字符处理,而另一些C实现却会自动地截断一个长标识符名称的尾部。连接器也会对它们能够处理的名称强加限制,例如外部名称中只允许使用大写字母。C实现者在面对这样的限制时,一个合理的选择就是强制所有的外部名称必须是大写。
  因为这个原因,为了保证程序的可移植性,谨慎地选择外部标识符的名称是重要的。比如说,两个函数的名称分别为 printf_fields 与 printf_float,这样的命名方式就不恰当;同理,使用 State 与 STATE 这样的命名方式也不明智。
  下面这个例子多少有些让人吃惊,考虑以下函数:

    char    *Malloc(unisgned n)
    {
        char    *p, *malloc(unsigned);

        p = malloc(n);
        if (p == NULL)
        {
            printf("out of memory");
            exit(-1);
        }
        return p;
    }

  上面的例子程序演示了一个确保检测到内存耗尽的异常情况的简单办法:在程序中应该调用 malloc 函数分配内存的地方,改为调用 Malloc 函数。这样,客户程序就不必在每次调用 malloc 函数时都需要进行检查。
  然而,考虑以下如果这个函数在一个不区分外部名称大小写的C语言中实现,将会发生怎样的情况呢?此时,函数 Malloc 与 malloc 实际上是等同的,也就是说,库函数 malloc 将被上面的 Malloc 函数等效替换。当在 Malloc 函数中调用库函数 malloc 时,实际上调用的却是 malloc 函数本身!

7.3 整数的大小

  C语言中为编程者提供了3种不同长度的整数:short 型、int 型和 long 型,C语言中的字符行为方式与小整数相似。C语言的定义对各种不同类型整数的相对长度作了一些规定:

    1. short 型整数容纳的值肯定能够被 int 型整数容纳,int 型整数容纳的值也肯定能够被 long 型整数容纳。
    1. 字符长度由硬件特性决定。
        因此,不同机器的字符长度不同,有8位的,有9位的,也有16位的。假如一个数组需要存放的数值数量不确定,可能是几十个,也可能是千万数量级,它的类型应该选择什么呢?要定义一个这样的变量,可移植性最好的办法就是声明该变量为 long 型,但在这种情况下我们定义一个“新的”类型无疑更为清晰:
    typedef    long    tenmil;

  当数组需要存放的数量变为几十个时,只需修改 tenmil 的类型定义为 char 型即可。

7.4 字符是有符号整数还是无符号整数

  现代大多数计算机都支持8位字符,因此大多数现代C编译器都把字符实现为8位整数。然而,并非所有的编译器都按照同样的方式来解释这些8位数值。
  编译器在转换 char 类型到 int 类型时,需要做出选择:应该将字符作为有符号数还是作为无符号数处理?如果是前一种情况,编译器在将 char 类型的数扩展到 int 类型时,应该同时复制符号位;而如果是后一种情况,编译器只需在多余的位上直接填充0即可。
  与之相关的一个常见错误认识是:如果 c 是一个字符变量,使用 (unsigned) c 就可得到与 c 等价的无符号整数。这是会失败的,因为在将字符 c 转换为无符号整数时, c 将首先被转换为 int 型整数,而此时可能得到非预期的后果。
  正确的方式是使用语句 (unsigned char) c,因为一个 unsigned char 类型的字符在转换为无符号整数时无需首先转换为 int 型整数,而是直接进行转换。

7.5 移位运算符

  使用移位运算符的程序员经常对这样两个问题感到困惑:

    1. 在向右移位时,空出的位是由 0 填充,还是由符号位的副本填充?
    1. 移位计数(即移位操作的位数)允许的取值范围是什么?
        第一个问题的答案: 如果被移位的对象是无符号数,那么空出的位将被 0 填充。如果被移位的对象是有符号数,那么C语言实现既可以用 0 填充空出的位,也可以用符号位的副本填充空出的位。
        第二个问题的答案:如果被移位的对象长度是 n 位,那么移位计数必须大于或等于0,而严格小于 n。因此,不可能做到在单次操作中将某个数值中的所有位都移出。
        需要注意的是,有符号整数的向右移位运算并不等同于除以 2 的某次幂,例如, (-1)>>1,这个操作的结果一般不可能为0,但是 (-1)/2 在大多数C实现上求值结果都是0。

7.6 内存位置0

  NULL 指针并不指向任何对象。因此,除非是用于赋值或比较运算,出于其他任何目的使用NULL指针都是非法的。例如,如果 p 或 q 是一个NULL指针,那么strcmp(p,q)的值就是未定义的。
  在这种情况下究竟会得到什么结果呢?不同的编译器有不同的结果。某些C语言实现对内存位置 0 强加了硬件级的读保护,在其上工作的程序如果错误使用了一个NULL指针,将立即终止执行。其它一些C语言实现对内存位置 0 只允许读,不允许写。
  严格来说,这并非一个可移植问题:在所有的C程序中,误用NULL指针的效果都是未定义的。然而,这样的程序有可能在某个C语言实现上“似乎”能够工作,只有当该程序转换到另一台机器上运行时才会暴露出问题来。
  要检查出这类问题的最简单办法就是,把程序移到允许读取内存位置 0 的机器上运行。下面的程序将揭示出某个C语言实现是如何处理内存地址 0 的:

    #include <stdio.h>

    main()
    {
        char    *p;
        p = NULL;
        printf("location 0 contains %d\n",*p);
    }

  在禁止读取内存地址 0 的机器上,这个程序将会执行失败。在其他机器上,这个程序将会以10进制的格式打印出内存位置 0 中存储的字符内容。

7.7 随机数的大小

  C语言提供了一个称为 rand 的函数,该函数的作用是产生一个(伪)随机非负整数。随机数的取值范围跟机器的整数长度有关,不同机器的随机数取值范围不同。当机器的整数长度为16位时, rand 函数将返回一个介于 0 到 2^15-1 之间的整数。
  这样造成的后果时,如果我们的程序中用到了 rand 函数,在移植时就必须根据特定的C语言实现作出“裁剪”。ANSI C标准中定义了一个常数 RAND_MAX,它的值等于随机值的最大取值。

7.8 大小写转换

  库函数 toupper 和 tolower 也有与随机数类似的历史。它们起初被实现为宏。有一次,AT&T软件开发部门的一个极具创新精神的人注意到,大多数 toupper 和 tolower 的使用都需要首先进行检查以保证参数是合适的。慎重考虑之后,他决定把这些宏重写如下:

    #define    toupper(c)    ( (c)>='a' && (c)<='z' ? (c)+'A'-'a' : (c) )
    #define    toupper(c)    ( (c)>='A' && (c)<='Z' ? (c)+'a'-'A' : (c) )    

  他又意识到这样做有可能在每次宏调用时,导致 c 被求值 1 到 3 次。如果遇到类似 toupper(p++)这样的表达式,可能造成不良后果。因此,他决定重写 toupper 和 tolower 为函数,重写后的 toupper 函数看上去大致像这样:

    int    toupper(int    c)
    {
        if( c>='a' && c<='z' )
            return    c+'A'-'a';
        return    c;
    }

  这样改动之后程序的健壮性无疑得到了加强,而代价是每次使用这些函数时却又引入了函数调用的开销。他意识到某些人也许不愿意付出效率方面损失的代价,因此他又重写引入了这些宏,不过使用了新的宏名:

    #define    _toupper(c)    ( (c)>='a' && (c)<='z' ? (c)+'A'-'a' : (c) )
    #define    _toupper(c)    ( (c)>='A' && (c)<='Z' ? (c)+'a'-'A' : (c) )    

  这样,宏的使用者就可以在速度与方便之间自由选择。

  看到这里,读者也许会叹一口气,为了满足可移植性,需要做的工作太多了!我们为什么要如此不辞辛劳地精益求精地修改呢?因为我们所处的是一个编程环境不断改变的世界,尽管软件看上去不像硬件那么实在,但大多数软件的生命周期却要长于它运行其上的硬件。而且,我们很难预测未来硬件的特性。因此,努力提高软件的可移植性,实际上是延迟了软件的生命周期。

[C陷阱和缺陷] 第7章 可移植性缺陷的更多相关文章

  1. c缺陷与陷阱笔记-第七章 可移植性代码

    1.移位运算符 如果被移位的对象长度是n位,那么移位计数必须>=0,并且<n,例如对于1个32位的数,移位运算n<<31和n<<0是OK的,n<<32和 ...

  2. [C陷阱和缺陷] 第3章 语义“陷阱”

    第3章 语义"陷阱"     一个句子哪怕其中的每个单词都拼写正确,而且语法也无懈可击,仍然可能有歧义或者并非书写者希望表达的意思.程序也有可能表面上是一个意思,而实际上的意思却相 ...

  3. [C陷阱和缺陷] 第1章 词法“陷阱”

    有感自己的C语言在有些地方存在误区,所以重新仔细把"C陷阱和缺陷"翻出来看看,并写下这篇博客,用于读书总结以及日后方便自身复习. 第1章 词法"陷阱" 1.1 ...

  4. [C陷阱和缺陷] 第6章 预处理器

      在严格意义上的编译过程开始之前,C语言预处理器首先对程序代码作了必要的转换处理.因此,我们运行的程序实际上并不是我们所写的程序.预处理器使得编程者可以简化某些工作,它的重要性可以由两个主要的原因说 ...

  5. [C陷阱和缺陷] 第2章 语法“陷阱”

    第2章 语法陷阱 2.1 理解函数声明   当计算机启动时,硬件将调用首地址为0位置的子例程,为了模拟开机时的情形,必须设计出一个C语言,以显示调用该子例程,经过一段时间的思考,得出语句如下: ( * ...

  6. [C陷阱和缺陷] 第5章 库函数

      有关库函数的使用,我们能给出的最好建议是尽量使用系统头文件,当然也可以自己造轮子,随个人喜好.本章将探讨某些常用的库函数,以及编程者在使用它们的过程中可能出错之处.   5.1 返回整数的getc ...

  7. [C陷阱和缺陷] 第4章 连接

    一个C程序可能是由多个分别编译的部分组成,这些不同部分通过连接器合并成一个整体.在本章中,我们将考查一个典型的连接器,注意它是如何对C程序进行处理的,从而归纳出一些由于连接器的特点而可能导致的错误. ...

  8. 软件测试价值提升之路- 第三章"拦截缺陷 "读书笔记

    作为一个测试团队,基本的职责是:测试产品,发现缺陷,报告结果,使每个版本的测试水准稳步提升.这些价值是作为一个测试所必须具备的,发挥这些价值能够让测试获得研发团队的基本信任.这类价值分为3部分: 1) ...

  9. 交换机安全学习笔记 第五章 DHCP缺陷攻击

    关于DHCP攻击有如下几类攻击方式:   一.耗尽DHCP地址池    通过随机生成源MAC地址,然后伪造DHCPDISCOVER数据包.耗尽DHCP服务器地址池.   免费的攻击工具:  Yersi ...

随机推荐

  1. JS前端取得并解析后台服务器返回的JSON数据的方法

    摘要:主要介绍:使用eval函数解析JSON数据:$.getJSON()方法获得服务器返回的JSON数据 JavaScript eval() 函数 eval(string) 函数可计算某个字符串,并执 ...

  2. string数据类型操作【四】

    keys *    用于查找所有的key值 exists mykey     #判断该键是否存在,存在返回1,否则返回0. del mykey        删除键(存在就删除返回1,不存在返回为0) ...

  3. 静态区间第k大(归并树)

    POJ 2104为例 思想: 利用归并排序的思想: 建树过程和归并排序类似,每个数列都是子树序列的合并与排序. 查询过程,如果所查询区间完全包含在当前区间中,则直接返回当前区间内小于所求数的元素个数, ...

  4. 深入理解hadoop(三)

    Hadoop多用户作业调度器 hadoop 最初是为批处理作业设计的,当时只采用了一个简单的FIFO调度机制分配任务,随着hadoop的普及以及应用的用户越来越多,基于FIFO的单用户调度机制不能很好 ...

  5. Word Search(深度搜索DFS,参考)

    Given a 2D board and a word, find if the word exists in the grid. The word can be constructed from l ...

  6. paramiko错误信息:Paramiko error: size mismatch in put

    在使用paramiko的put往远处服务器上传资源的时候,出现类似下面的错误信息 The code in paramiko's sftp_client.py:putfo() reads at the ...

  7. 【转】实现一个自己的promise

    转, 原文:http://blog.csdn.net/yibingxiong1/article/details/68075416------------------------------------ ...

  8. UVa 10950 - Bad Code

    题目:有一种编码方式.串仅仅有小写字母构成,每一个小写字母相应一个数字,如今给你妆化后的数字串, 问有多少个原串与之相应,注意数字串里可能有一个前导0. 分析:搜索.按字母顺序存储映射表,按字母顺序匹 ...

  9. Cocos2dx--开发环境搭建

    配置文档下载:http://pan.baidu.com/s/1rja8I 有些文件比較大的自己去下,这里列一下全部用到的文件 adt-bundle-windows-x86-20140321.zip a ...

  10. 湖南省第九届大学生计算机程序设计竞赛 Interesting Calculator

    Interesting Calculator Time Limit: 2 Sec  Memory Limit: 128 MB Submit: 163  Solved: 49 Description T ...