-
Notifications
You must be signed in to change notification settings - Fork 25
/
ch15s01.html
21 lines (21 loc) · 19.2 KB
/
ch15s01.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /><title>1. 整型</title><link rel="stylesheet" href="styles.css" type="text/css" /><meta name="generator" content="DocBook XSL Stylesheets V1.73.2" /><link rel="start" href="index.html" title="Linux C编程一站式学习" /><link rel="up" href="ch15.html" title="第 15 章 数据类型详解" /><link rel="prev" href="ch15.html" title="第 15 章 数据类型详解" /><link rel="next" href="ch15s02.html" title="2. 浮点型" /></head><body><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">1. 整型</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch15.html">上一页</a> </td><th width="60%" align="center">第 15 章 数据类型详解</th><td width="20%" align="right"> <a accesskey="n" href="ch15s02.html">下一页</a></td></tr></table><hr /></div><div class="sect1" lang="zh-cn" xml:lang="zh-cn"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a id="id2756569"></a>1. 整型</h2></div></div></div><p>我们知道,在C语言中<code class="literal">char</code>型占一个字节的存储空间,一个字节通常是8个bit。如果这8个bit按无符号整数来解释,取值范围是0~255,如果按有符号整数来解释,采用2's Complement表示法,取值范围是-128~127。C语言规定了<code class="literal">signed</code>和<code class="literal">unsigned</code>两个关键字,<code class="literal">unsigned char</code>型表示无符号数,<code class="literal">signed char</code>型表示有符号数。</p><p>那么以前我们常用的不带<code class="literal">signed</code>或<code class="literal">unsigned</code>关键字的<code class="literal">char</code>型是无符号数还是有符号数呢?C标准规定这是Implementation Defined,编译器可以定义<code class="literal">char</code>型是无符号的,也可以定义<code class="literal">char</code>型是有符号的,在该编译器所对应的体系结构上哪种实现效率高就可以采用哪种实现,x86平台的<code class="literal">gcc</code>定义<code class="literal">char</code>型是有符号的。这也是C标准的Rationale之一:<span class="emphasis"><em>优先考虑效率,而可移植性尚在其次</em></span>。这就要求程序员非常清楚这些规则,如果你要写可移植的代码,就必须清楚哪些写法是不可移植的,应该避免使用。另一方面,写不可移植的代码有时候也是必要的,比如Linux内核代码使用了很多只有<code class="literal">gcc</code>支持的语法特性以得到最佳的执行效率,在写这些代码的时候就没打算用别的编译器编译,也就没考虑可移植性的问题。如果要写不可移植的代码,你也必须清楚代码中的哪些部分是不可移植的,以及为什么要这样写,如果不是为了效率,一般来说就没有理由故意写不可移植的代码。从现在开始,我们会接触到很多Implementation Defined的特性,C语言与平台和编译器是密不可分的,离开了具体的平台和编译器讨论C语言,就只能讨论到本书第一部分的程度了。注意,ASCII码的取值范围是0~127,所以不管<code class="literal">char</code>型是有符号的还是无符号的,存一个ASCII码都没有问题,一般来说,如果用<code class="literal">char</code>型存ASCII码字符,就不必明确写是<code class="literal">signed</code>还是<code class="literal">unsigned</code>,如果用<code class="literal">char</code>型表示8位的整数,为了可移植性就必须写明是<code class="literal">signed</code>还是<code class="literal">unsigned</code>。</p><div class="note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Implementation-defined、Unspecified和Undefined</h3><p>在C标准中没有做明确规定的地方会用Implementation-defined<a id="id2756724" class="indexterm"></a>、Unspecified<a id="id2756731" class="indexterm"></a>或Undefined<a id="id2756738" class="indexterm"></a>来表述,在本书中有时把这三种情况统称为“<span class="quote">未明确定义</span>”的。这三种情况到底有什么不同呢?</p><p>我们刚才看到一种Implementation-defined的情况,C标准没有明确规定<code class="literal">char</code>是有符号的还是无符号的,但是要求编译器必须对此做出明确规定,并写在编译器的文档中。</p><p>而对于Unspecified的情况,往往有几种可选的处理方式,C标准没有明确规定按哪种方式处理,编译器可以自己决定,并且也不必写在编译器的文档中,这样即便用同一个编译器的不同版本来编译也可能得到不同的结果,因为编译器没有在文档中明确写它会怎么处理,那么不同版本的编译器就可以选择不同的处理方式,比如下一章我们会讲到一个函数调用的各个实参表达式按什么顺序求值是Unspecified的。</p><p>Undefined的情况则是完全不确定的,C标准没规定怎么处理,编译器很可能也没规定,甚至也没做出错处理,有很多Undefined的情况编译器是检查不出来的,最终会导致运行时错误,比如数组访问越界就是Undefined的。</p><p>初学者看到这些规则通常会很不舒服,觉得这不是在学编程而是在啃法律条文,结果越学越泄气。是的,C语言并不像一个数学定理那么完美,现实世界里的东西总是不够完美的。但还好啦,C程序员已经很幸福了,只要严格遵照C标准来写代码,不要去触碰那些阴暗角落,写出来的代码就有很好的可移植性。想想那些可怜的JavaScript程序员吧,他们甚至连一个可以遵照的标准都没有,一个浏览器一个样,甚至同一个浏览器的不同版本也差别很大,程序员不得不为每一种浏览器的每一个版本分别写不同的代码。</p></div><p>除了<code class="literal">char</code>型之外,整型还包括<code class="literal">short int</code>(或者简写为<code class="literal">short</code>)、<code class="literal">int</code>、<code class="literal">long int</code>(或者简写为<code class="literal">long</code>)、<code class="literal">long long int</code>(或者简写为<code class="literal">long long</code>)等几种<sup>[<a id="id2756873" href="#ftn.id2756873" class="footnote">25</a>]</sup>,这些类型都可以加上<code class="literal">signed</code>或<code class="literal">unsigned</code>关键字表示有符号或无符号数。其实,对于有符号数在计算机中的表示是Sign and Magnitude、1's Complement还是2's Complement,C标准也没有明确规定,也是Implementation Defined。大多数体系结构都采用2's Complement表示法,x86平台也是如此,从现在开始我们只讨论2's Complement表示法的情况。还有一点要注意,除了<code class="literal">char</code>型以外的这些类型如果不明确写<code class="literal">signed</code>或<code class="literal">unsigned</code>关键字都表示<code class="literal">signed</code>,这一点是C标准明确规定的,不是Implementation Defined。</p><p>除了<code class="literal">char</code>型在C标准中明确规定占一个字节之外,其它整型占几个字节都是Implementation Defined。通常的编译器实现遵守ILP32<a id="id2756951" class="indexterm"></a>或LP64<a id="id2756958" class="indexterm"></a>规范,如下表所示。</p><div class="table"><a id="id2756966"></a><p class="title"><b>表 15.1. ILP32和LP64</b></p><div class="table-contents"><table summary="ILP32和LP64" border="1"><colgroup><col /><col /><col /></colgroup><thead><tr><th>类型</th><th>ILP32(位数)</th><th>LP64(位数)</th></tr></thead><tbody><tr><td>char</td><td>8</td><td>8</td></tr><tr><td>short</td><td>16</td><td>16</td></tr><tr><td>int</td><td>32</td><td>32</td></tr><tr><td>long</td><td>32</td><td>64</td></tr><tr><td>long long</td><td>64</td><td>64</td></tr><tr><td>指针</td><td>32</td><td>64</td></tr></tbody></table></div></div><br class="table-break" /><p>ILP32这个缩写的意思是<code class="literal">int</code>(I)、<code class="literal">long</code>(L)和指针(P)类型都占32位,通常32位计算机的C编译器采用这种规范,x86平台的<code class="literal">gcc</code>也是如此。LP64是指<code class="literal">long</code>(L)和指针占64位,通常64位计算机的C编译器采用这种规范。指针类型的长度总是和计算机的位数一致,至于什么是计算机的位数,指针又是一种什么样的类型,我们到<a class="xref" href="ch17.html#arch">第 17 章 <i>计算机体系结构基础</i></a>和<a class="xref" href="ch23.html#pointer">第 23 章 <i>指针</i></a>再分别详细解释。从现在开始本书做以下约定:<span class="emphasis"><em>在以后的陈述中,缺省平台是x86/Linux/gcc,遵循ILP32,并且<code class="literal">char</code>是有符号的,我不会每次都加以说明,但说到其它平台时我会明确指出是什么平台</em></span>。</p><p>在<a class="xref" href="ch02s02.html#expr.constant">第 2 节 “常量”</a>讲过C语言的常量有整数常量、字符常量、枚举常量和浮点数常量四种,其实字符常量和枚举常量的类型都是<code class="literal">int</code>型,因此前三种常量的类型都属于整型。整数常量有很多种,不全是<code class="literal">int</code>型的,下面我们详细讨论整数常量。</p><p>以前我们只用到十进制的整数常量,其实在C语言中也可以用八进制和十六进制的整数常量<sup>[<a id="id2757137" href="#ftn.id2757137" class="footnote">26</a>]</sup>。八进制整数常量以0开头,后面的数字只能是0~7,例如022,因此十进制的整数常量就不能以0开头了,否则无法和八进制区分。十六进制整数常量以0x或0X开头,后面的数字可以是0~9、a~f和A~F。在<a class="xref" href="ch02s06.html#expr.charencoding">第 6 节 “字符类型与字符编码”</a>讲过一种转义序列,以\或\x加八进制或十六进制数字表示,这种表示方式相当于把八进制和十六进制整数常量开头的0替换成\了。</p><p>整数常量还可以在末尾加u或U表示“<span class="quote">unsigned</span>”,加l或L表示“<span class="quote">long</span>”,加ll或LL表示“<span class="quote">long long</span>”,例如0x1234U,98765ULL等。但事实上u、l、ll这几种后缀和上面讲的<code class="literal">unsigned</code>、<code class="literal">long</code>、<code class="literal">long long</code>关键字并不是一一对应的。这个对应关系比较复杂,准确的描述如下表所示(出自<a class="xref" href="bi01.html#bibli.c99" title="ISO/IEC 9899: Programming Languages - C">[<abbr class="abbrev">C99</abbr>]</a>条款6.4.4.1)。</p><div class="table"><a id="id2757215"></a><p class="title"><b>表 15.2. 整数常量的类型</b></p><div class="table-contents"><table summary="整数常量的类型" border="1"><colgroup><col /><col /><col /></colgroup><thead><tr><th>后缀</th><th>十进制常量</th><th>八进制或十六进制常量</th></tr></thead><tbody><tr><td>无</td><td><div class="literallayout"><p>int<br />
long int<br />
long long int</p></div></td><td><div class="literallayout"><p>int<br />
unsigned int<br />
long int<br />
unsigned long int<br />
long long int<br />
unsigned long long int</p></div></td></tr><tr><td>u或U</td><td><div class="literallayout"><p>unsigned int<br />
unsigned long int<br />
unsigned long long int</p></div></td><td><div class="literallayout"><p>unsigned int<br />
unsigned long int<br />
unsigned long long int</p></div></td></tr><tr><td>l或L</td><td><div class="literallayout"><p>long int<br />
long long int</p></div></td><td><div class="literallayout"><p>long int<br />
unsigned long int<br />
long long int<br />
unsigned long long int</p></div></td></tr><tr><td>既有u或U,又有l或L</td><td><div class="literallayout"><p>unsigned long int<br />
unsigned long long int</p></div></td><td><div class="literallayout"><p>unsigned long int<br />
unsigned long long int</p></div></td></tr><tr><td>ll或LL</td><td><div class="literallayout"><p>long long int</p></div></td><td><div class="literallayout"><p>long long int<br />
unsigned long long int</p></div></td></tr><tr><td>既有u或U,又有ll或LL</td><td><div class="literallayout"><p>unsigned long long int</p></div></td><td><div class="literallayout"><p>unsigned long long int</p></div></td></tr></tbody></table></div></div><br class="table-break" /><p>给定一个整数常量,比如1234U,那么它应该属于“<span class="quote">u或U</span>”这一行的“<span class="quote">十进制常量</span>”这一列,这个表格单元中列了三种类型<code class="literal">unsigned int</code>、<code class="literal">unsigned long int</code>、<code class="literal">unsigned long long int</code>,从上到下找出第一个足够长的类型可以表示1234这个数,那么它就是这个整数常量的类型,如果<code class="literal">int</code>是32位的那么<code class="literal">unsigned int</code>就可以表示。</p><p>再比如0xffff0000,应该属于第一行“<span class="quote">无</span>”的第二列“<span class="quote">八进制或十六进制常量</span>”,这一列有六种类型<code class="literal">int</code>、<code class="literal">unsigned int</code>、<code class="literal">long int</code>、<code class="literal">unsigned long int</code>、<code class="literal">long long int</code>、<code class="literal">unsigned long long int</code>,第一个类型<code class="literal">int</code>表示不了0xffff0000这么大的数,我们写这个十六进制常量是要表示一个正数,而它的MSB(第31位)是1,如果按有符号<code class="literal">int</code>类型来解释就成了负数了,第二个类型<code class="literal">unsigned int</code>可以表示这个数,所以这个十六进制常量的类型应该算<code class="literal">unsigned int</code>。所以请注意,0x7fffffff和0xffff0000这两个常量虽然看起来差不多,但前者是<code class="literal">int</code>型,而后者是<code class="literal">unsigned int</code>型。</p><p>讲一个有意思的问题。我们知道x86平台上<code class="literal">int</code>的取值范围是-2147483648~2147483647,那么用<code class="literal">printf("%d\n", -2147483648);</code>打印<code class="literal">int</code>类型的下界有没有问题呢?如果用<code class="literal">gcc main.c -std=c99</code>编译会有警告信息:<code class="literal">warning: format ‘%d’ expects type ‘int’, but argument 2 has type ‘long long int’</code>。这是因为,虽然-2147483648这个数值能够用<code class="literal">int</code>型表示,但在C语言中却没法写出对应这个数值的<code class="literal">int</code>型常量,C编译器会把它当成一个整数常量2147483648和一个负号运算符组成的表达式,而整数常量2147483648已经超过了<code class="literal">int</code>型的取值范围,在x86平台上<code class="literal">int</code>和<code class="literal">long</code>的取值范围相同,所以这个常量也超过了<code class="literal">long</code>型的取值范围,根据上表第一行“<span class="quote">无</span>”的第一列<code class="literal">十进制常量</code>,这个整数常量应该算<code class="literal">long long</code>型的,前面再加个负号组成的表达式仍然是<code class="literal">long long</code>型,而<code class="literal">printf</code>的<code class="literal">%d</code>转换说明要求后面的参数是<code class="literal">int</code>型,所以编译器报警告。之所以编译命令要加<code class="literal">-std=c99</code>选项是因为C99以前对于整数常量的类型规定和上表有一些出入,即使不加这个选项也会报警告,但警告信息不准确,读者可以试试。如果改成<code class="literal">printf("%d\n", -2147483647-1);</code>编译器就不会报警告了,-号运算符的两个操作数-2147483647和1都是<code class="literal">int</code>型,计算结果也应该是<code class="literal">int</code>型,并且它的值也没有超出<code class="literal">int</code>型的取值范围;或者改成<code class="literal">printf("%lld\n", -2147483648);</code>也可以,转换说明<code class="literal">%lld</code>告诉<code class="literal">printf</code>后面的参数是<code class="literal">long long</code>型,有些转换说明格式目前还没讲到,详见<a class="xref" href="ch25s02.html#stdlib.formatio">第 2.9 节 “格式化I/O函数”</a>。</p><p>怎么样,整数常量没有你原来想的那么简单吧。再看一个不简单的问题。<code class="literal">long long i = 1234567890 * 1234567890;</code>编译时会有警告信息:<code class="literal">warning: integer overflow in expression</code>。1234567890是<code class="literal">int</code>型,两个<code class="literal">int</code>型相乘的表达式仍然是<code class="literal">int</code>型,而乘积已经超过<code class="literal">int</code>型的取值范围了,因此提示计算结果溢出。如果改成<code class="literal">long long i = 1234567890LL * 1234567890;</code>,其中一个常量是<code class="literal">long long</code>型,另一个常量也会先转换成<code class="literal">long long</code>型再做乘法运算,两数相乘的表达式也是<code class="literal">long long</code>型,编译器就不会报警告了。有关类型转换的规则将在<a class="xref" href="ch15s03.html#type.conversion">第 3 节 “类型转换”</a>详细介绍。</p><div class="footnotes"><br /><hr width="100" align="left" /><div class="footnote"><p><sup>[<a id="ftn.id2756873" href="#id2756873" class="para">25</a>] </sup>我们在<a class="xref" href="ch19s04.html#asmc.structunion">第 4 节 “结构体和联合体”</a>还要介绍一种特殊的整型--Bit-field。</p></div><div class="footnote"><p><sup>[<a id="ftn.id2757137" href="#id2757137" class="para">26</a>] </sup>有些编译器(比如<code class="literal">gcc</code>)也支持二进制的整数常量,以0b或0B开头,比如0b0001111,但二进制的整数常量从未进入C标准,只是某些编译器的扩展,所以不建议使用,由于二进制和八进制、十六进制的对应关系非常明显,用八进制或十六进制常量完全可以代替使用二进制常量。</p></div></div></div><div class="navfooter"><hr /><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="ch15.html">上一页</a> </td><td width="20%" align="center"><a accesskey="u" href="ch15.html">上一级</a></td><td width="40%" align="right"> <a accesskey="n" href="ch15s02.html">下一页</a></td></tr><tr><td width="40%" align="left" valign="top">第 15 章 数据类型详解 </td><td width="20%" align="center"><a accesskey="h" href="index.html">起始页</a></td><td width="40%" align="right" valign="top"> 2. 浮点型</td></tr></table></div></body></html>