读谭师傅的书(7) 读谭师傅的书(9)
Mar 27

之前已经学习了第三章3.1节和3.2节。现在接着学习第三章。

第3.3节

1、谭师傅花了很大一段讲什么是补码。我认为讲得还算清楚。不过,很可惜的是,整型数据在内存中如何存放,这不是标准C的一部分,而是取决于具体的硬件。为了让C程序可移植,ANSI C标准中从来没有规定过数据在内存中如何存放,将来也不可能。

2、谭师傅一会说有三种整型,一会又说有六种。这也就罢了。在学习3.2节时,我们已经提到过,整型应该包括字符、枚举,因为C语言里并不把这几种类型区别对待。像谭师傅这样讲法,有可能让人以为字符和枚举是什么特殊类型。

3、同上面第一点,有符号整型的第一位是否是符号位同样取决于具体硬件。

4、谭师傅说,一个int变量取值范围是-32768 ~ 32767。很可惜,这也是错的。一个int变量取值范围随编译环境不同而不同。ANSI C规定编译器必须提供一个<limits.h>,其中包含各种整数类型的大小。ANSI C还规定,对于int类型,最小的取值范围是 -32767 ~ 32767。谭师傅还不厌其烦地列出了TC的各种整型数据取值范围。这些在TC上是成立的。如果换了一个系统,就只有自求多福了。

5、谭师傅还花了一个小节来讲整型数据溢出时会发生什么事。这里的这句“将变量b改成long型…“已经多为人诟病,就不再提了。但其实最关键的问题在于,当数据溢出时会发生什么事,实际上在ANSI C标准中是undefined,也就是说发生任何事都有可能,取决于具体的编译、运行环境。

第3.4节

6、对于浮点型,同样,标准C并不要求数据在内存中以何种形式、用多少空间来存放。

7、谭师傅教导我们说,“ANSI C并未具体规定每种类型数据的长度、精度和数值范围”。实际上,ANSI C要求编译器提供一个<float.h>,其中必须包括关于浮点类型的信息,并对最低精度作了规定。比如,标准C要求float类型最少有6位有效数字,最少能表达10的37次方大小的数字,能表达的最小正整数是10的负5次方,等等。

8、标准C并不要求double的精度一定比float高,long double精度一定比double高,而只规定了这三种类型后一种精度不能比前一种低。

9、谭师傅说,“用程序计算1.0/3.0*3的结果并不等于1“。很遗憾的是,这又一次证明了谭师傅喜欢想当然,不上机实验。TC运行结果,1.0/3.0*3确实等于1。虽然谭师傅讲的关于精度的问题确实值得注意,但用错误的例子并不能说明问题。

小结一下:把可移植的C变成了不可移植的C,谭师傅功不可没。

好了,第3.3和3.4节学习完了,下次接着学

“读谭师傅的书(8)”有7篇评论

  1. 基本 Says:

    C99确实讨论了整数的表达。如6.2.6.2 Integer types的第二段,

    2 For signed integer types, the bits of the object representation
    shall be divided into three groups: value bits, padding bits,
    and the sign bit. There need not be any padding bits; there shall
    be exactly one sign bit. Each bit that is a value bit shall have
    the same value as the same bit in the object representation of
    the corresponding unsigned type (if there are M value bits in
    the signed type and N in the unsigned type, then M ≤ N ). If the
    sign bit is zero, it shall not affect the resulting value. If the
    sign bit is one, the value shall be modified in one of the following
    ways:
    — the corresponding value with sign bit 0 is negated (sign and magnitude);
    — the sign bit has the value -(2**N) (two’s complement);
    — the sign bit has the value -(2**N - 1) (ones’ complement ).
    Which of these applies is implementation-defined, as is whether
    the value with sign bit 1 and all value bits zero (for the first two),
    or with sign bit and all value bits 1 (for ones’ complement), is a
    trap representation or a normal value. In the case of sign and magnitude
    and ones’ complement, if this representation is a normal value it is
    called a negative zero.

    尤其是新规定了一组定宽整数型,如int8_t等,必须用补码表示:

    7.18.1.1 Exact-width integer types
    1 The typedef name intN_t designates a signed integer type with width N ,
    no padding bits, and a two’s complement representation. Thus, int8_t
    denotes a signed integer type with a width of exactly 8 bits.
    2 The typedef name uintN_t designates an unsigned integer type with
    width N . Thus, uint24_t denotes an unsigned integer type with a width
    of exactly 24 bits.
    3 These types are optional. However, if an implementation provides
    integer types with widths of 8, 16, 32, or 64 bits, no padding bits,
    and (for the signed types) that have a two’s complement representation,
    it shall define the corresponding typedef names.


    这些如果是在网上找的N1256,那是在C99基础上修改过的draft。讨论整数的表达方式并不代表规定整数在内存里的存放方式。而且这里讲的内容还是属于implementation dependent,也就是说不可移植。–eng

  2. eng Says:

    回基本:你的理解有问题。if an implementation provides integer types … it shall define the corresponding typedef names. 并不排除一个implementation提供一个one’s complement的uint8_t类型的可能。总之,任何对于数据在内存中如何存放的假设都是不可靠的。

  3. 基本 Says:

    确实,我没有C89或C99的正式标准,只是在网上找到一些草案,因此不能百分之百地
    认定正式标准跟该草案一模一样,但有理由相信对我们讨论的这部分很接近。

    补码准确地说是关于型的表达,和“内存”(我的理解是memory)隔了一层,更接近CPU
    和register。整数的表达虽然“is implementation-defined”,可规定只能是那几
    种binary表达,范围很窄,“The restriction to binary numeration systems
    rules out such curiosities as Gray code and makes 15 possible arithmetic
    definitions of the bitwise operators on unsigned types.”(见Rationale for
    International Standard Programming Languages C, Revision 5.10, April-2003)
    讨论补码不仅很有帮助,而且对理解C必不可少。C有可移植的部分,也有不可移植的
    部分,二者都重要,讲清楚就行。

    至于an implemention能不能“提供一个one’s complement的uint8_t类型”呢?答
    案是不能。我上面引用的7.18.1.1的第一段规定了int8_t必须是two’s complement。
    第三段是说int8_t等可有可无,但只要an implementation提供满足条件的整数型
    (条件之一是two’s complement),那么该implementation就应有int8_t等型,ones
    complement根本不在考虑之列。

  4. eng Says:

    回基本,我去查了一下,你“基本”是对的:)不过你少引了7.18.1前面的一段,里面规定如果compiler不提供这些类型,就不能定义intN_t这样的类型名。这段和后面这个第三段合在一起才能说明问题。只看后面这段,不能排除有的compiler定义一个one’s complement的int8_t类型的可能。

    谭师傅的主要问题,正是没有讲清楚什么是可移植的,什么是不可移植的。

  5. 基本 Says:

    在网上找到三份(post-)C99 drafts,我上面完整地引用了7.18.1.1,
    共三段,其中第1段就排出了ones’ complement式int8_t型的可能。

    在7.18.1下,7.18.1.1前的两段是
    1 When typedef names differing only in the absence or
    presence of the initial u are defined, they shall denote
    corresponding signed and unsigned types as described
    in 6.2.5; an implementation providing one of these
    corresponding types shall also provide the other.
    2 In the following descriptions, the symbol N represents
    an unsigned decimal integer with no leading zeros
    (e.g., 8 or 24, but not 04 or 048).
    段1讲以u开头的是unsigned的意思,如uint32_t;没有u的则是
    signed的意思,如int32_t。有uint32_t的话,就要有int32_t;
    反之亦然。段2则是关于int8_t,uint16_t等中间的数字。

    不知你说我少引用的是哪一段?

    这三份drafts分别是
      WG14/N1124,May 5,2005
      WG14/N1256,September 7,2007
      WG14/N1336,August 11,2008
    这些段落的文字完全一致,你查的如是1999年出的,我少引用是
    否在那里?据我所知,这一部分中, 仅在2004年出的TC2中改动
    了7.18.1.1的第3段,我上面引用的即改动后的文字。

  6. eng Says:

    回基本:我说的是7.18.1这一节之前的一段(N1256):
    For each type described herein that the implementation provides,224) shall declare that typedef name and define the associated macros. Conversely, for each type described herein that the implementation does not provide, shall not declare that typedef name nor shall it define the associated macros. An inplementation shall provide those types described as ‘‘required’’, but need not provide any of the others (described as ‘‘optional’’).

  7. 穆扬 Says:

    老谭这个地方还有几个硬伤
    比如“常量无unsigned”……

发表评论

CAPTCHA Image
*