首先看下C标准中“未明确定义”的三种类型Implementation-defined、Unspecified和Undefined。
Implementation-defined 的情况,是C 标准没有明确规定,但是要求编译器必须对此做出明确规定,并写在编译器的文档中。
Unspecified的情况,往往有几种可选的处理方式,C 标准没有明确规定按哪种方式处理,编译器可以自己决定,并且也不必写在编译器的文档中,这样即使用同一个编译器的不同版本来编译也可能得到不同的结果,因为编译器没有在文档中明确写它会怎么处理,那么不同版本的编译器就可以选择不同的处理方式,比如一个函数调用的各个实参表达式按什么顺序求值是Unspecified的。
Undefined的情况则是完全不确定的,C 标准没规定怎么处理,编译器很可能也没规定,甚至也没做出错处理,有很多Undefined的情况是编译器是检查不出来的,最终会导致运行时错误,比如数组访问越界就是Undefined的。
除了char型在C 标准中明确规定占一个字节之外,其它整数类型占几个字节都是Implementation Defined。通常的编译器实现遵守ILP32 或LP64规范。
LP32 这个缩写的意思是int (I )、long(L )和指针(P )类型都占32位,通常32位计算机的C 编译器采用这种规范,x86 平台的gcc 也是如此。LP64是指long(L )和指针占64位,通常64位计算机的C 编译器采用这种规范。指针类型的长度总是和计算机的位数一致。
(PS下,long long 类型对应的占位符和平台和编译器有关,linux中gcc很统一用%lld,在windows中,MinGW的gcc和VC6都需要用到%I64d,但VS2008却是%lld)
在limit.h文件中有比较详细的定义
/* Copyright (C) 1991, 1992, 1996, 1997, 1998, 1999, 2000, 2005 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. The GNU C Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with the GNU C Library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ /* * ISO C99 Standard: 7.10/5.2.4.2.1 Sizes of integer types <limits.h> */
*/ #ifndef _LIBC_LIMITS_H_ #define _LIBC_LIMITS_H_ 1 #include <features.h> /* Maximum length of any multibyte character in any locale. We define this value here since the gcc header does not define the correct value. */ #define MB_LEN_MAX 16 /* If we are not using GNU CC we have to define all the symbols ourself. Otherwise use gcc‘s definitions (see below). */ #if !defined __GNUC__ || __GNUC__ < 2 /* We only protect from multiple inclusion here, because all the other #include‘s protect themselves, and in GCC 2 we may #include_next through multiple copies of this file before we get to GCC‘s. */ # ifndef _LIMITS_H # define _LIMITS_H 1 #include <bits/wordsize.h> /* We don‘t have #include_next. Define ANSI <limits.h> for standard 32-bit words. */ /* These assume 8-bit `char‘s, 16-bit `short int‘s, and 32-bit `int‘s and `long int‘s. */ /* Number of bits in a `char‘. */ # define CHAR_BIT 8 /* Minimum and maximum values a `signed char‘ can hold. */ # define SCHAR_MIN (-128) # define SCHAR_MAX 127 /* Maximum value an `unsigned char‘ can hold. (Minimum is 0.) */ # define UCHAR_MAX 255 /* Minimum and maximum values a `char‘ can hold. */ # ifdef __CHAR_UNSIGNED__ # define CHAR_MIN 0 # define CHAR_MAX UCHAR_MAX # else # define CHAR_MIN SCHAR_MIN # define CHAR_MAX SCHAR_MAX # endif /* Minimum and maximum values a `signed short int‘ can hold. */ # define SHRT_MIN (-32768) # define SHRT_MAX 32767 /* Maximum value an `unsigned short int‘ can hold. (Minimum is 0.) */ # define USHRT_MAX 65535 /* Minimum and maximum values a `signed int‘ can hold. */ # define INT_MIN (-INT_MAX - 1) # define INT_MAX 2147483647 /* Maximum value an `unsigned int‘ can hold. (Minimum is 0.) */ # define UINT_MAX 4294967295U /* Minimum and maximum values a `signed long int‘ can hold. */ # if __WORDSIZE == 64 # define LONG_MAX 9223372036854775807L # else # define LONG_MAX 2147483647L # endif # define LONG_MIN (-LONG_MAX - 1L) /* Maximum value an `unsigned long int‘ can hold. (Minimum is 0.) */ # if __WORDSIZE == 64 # define ULONG_MAX 18446744073709551615UL # else # define ULONG_MAX 4294967295UL # endif # ifdef __USE_ISOC99 /* Minimum and maximum values a `signed long long int‘ can hold. */ # define LLONG_MAX 9223372036854775807LL # define LLONG_MIN (-LLONG_MAX - 1LL) /* Maximum value an `unsigned long long int‘ can hold. (Minimum is 0.) */ # define ULLONG_MAX 18446744073709551615ULL # endif /* ISO C99 */ # endif /* limits.h */ #endif /* GCC 2. */ #endif /* !_LIBC_LIMITS_H_ */ /* Get the compiler‘s limits.h, which defines almost all the ISO constants. We put this #include_next outside the double inclusion check because it should be possible to include this file more than once and still get the definitions from gcc‘s header. */ #if defined __GNUC__ && !defined _GCC_LIMITS_H_ /* `_GCC_LIMITS_H_‘ is what GCC‘s file defines. */ # include_next <limits.h> #endif /* The <limits.h> files in some gcc versions don‘t define LLONG_MIN, LLONG_MAX, and ULLONG_MAX. Instead only the values gcc defined for ages are available. */ #if defined __USE_ISOC99 && defined __GNUC__ # ifndef LLONG_MIN # define LLONG_MIN (-LLONG_MAX-1) # endif # ifndef LLONG_MAX # define LLONG_MAX __LONG_LONG_MAX__ # endif # ifndef ULLONG_MAX # define ULLONG_MAX (LLONG_MAX * 2ULL + 1) # endif #endif #ifdef __USE_POSIX /* POSIX adds things to <limits.h>. */ # include <bits/posix1_lim.h> #endif #ifdef __USE_POSIX2 # include <bits/posix2_lim.h> #endif #ifdef __USE_XOPEN # include <bits/xopen_lim.h> #endif
注意一点:对于不带signed 或unsigned 关键字的char型,C 标准规定这是Implementation Defined ,编译器可以定义char型是无符号的,也可以定义char型是有符号的,在该编译器所对应的体系结构上哪种实现效率高就可以采用哪种实现,x86 平台的gcc 定义char是有符号的。这也是C 标准的Rationale之一:优先考虑效率,而可移植性尚在其次。另外,除了char型以外的整数类型(short,int,long,long long)如果不明确写signed 或unsigned 关键字都表示有符号数,这一点是C 标准明确规定的。
有符号数在计算机中的表示形式是Sign and Magnitude 、1‘s Complement还是2‘s Complement?C 标准也没有明确规定,也是Implementation Defined 。大多数体系结构都采用2‘s Complement表示形式和加减运算规则,x86 平台也是如此。
注意,ASCII码的取值范围是0~127 ,所以不管char型是有符号的还是无符号的,存一个ASCII码都没有问题,一般来说,如果用char型存ASCII码字符,就不必明确写signed 还是unsigned ,如果把char型当作8 位的整数来用,为了可移植性就必须写明是signed 还是unsigned 。
如果不是为了效率,一般来说就没有理由故意写不可移植的代码。比如Linux内核代码使用了很多gcc 特性以得到最佳的执行效率,在写的时候就没打算用别的编译器编译,也就没考虑可移植性的问题。可见C 语言与平台和编译器是密不可分的,离开了具体的平台和编译器讨论C 语言必然难以深入
C 标准规定的浮点型有float、double 、long double ,和整数类型一样,既没有规定每种类型占多少字节,也没有规定采用哪种表示形式。浮点数的实现在各种平台上差异很大,x86 处理器通常是有浮点运算单元的,遵循IEEE 754,float型通常是32位,double 型通常是64位。要详细了解浮点数的IEEE表示可以查看CSAPP的第二章2.4.2 .具体的定义可以查看float.h
(文中部分内容摘自宋劲彬老师的文章)