4543|8

6366

帖子

4914

TA的资源

版主

楼主
 

C核心技术手册 [复制链接]

本文来自网络,作者:Socrates

本资料已经组合成文档,上传到“下载中心”了。欢迎大家下载学习
本文内容是翻译C-In a Nutshell这本书,还没有完全翻译完


C核心技术手册(一)
第一部分 语法
第一章    基础语法
本章节描述C语言的基本原理及特征。
1.1  C的特征
  C是一种有各方面用途的过程语言。Dennis Ritchie 在1970年在新泽西州的贝尔实验室首次设计了C语言,目的是为实现UNIX操作系统及其应用能够最大程度与特定的硬件分离。C语言的关键特性如下所述:
l  轻便的源码
l  操作硬件的能力
l  高效
  所以,UNIX开发者可以使用C语言编写操作系统的绝大部分,仅有很少的一些操作特有硬件部分由汇编而写。
  C的前身是Martin Richards发明的无类型编程语言BCPL(the Basic Combined Programming);
而由Ken Thompson发明的B语言由BCPL发展而来。C语言的一个特点就是它有丰富的数据类型,包括字符, 数字,数组、结构等等, 1978年, BrianKernighan 和 Dennis Ritchie发表了关于C语言的正式描述文档,成为第一个事实上的标准。他们的文档通常被简称为”K&R”。作为一个高度轻便的核心语言,C包含很少的依赖硬件的元素.例如,它不包括文件访问或动态内存管理,事实上,也不包含控制台输入输出的内容。这些其实均由标准的C语言lib库提供的函数来完成。
  这种语言设计使C的编译器相对紧凑,以至于很容易与新型的系统兼容。而且,一旦编译器在这些新系统上开始运行,你可以编译绝大多数标准库的函数,而不需要做修改,因为这些均由简洁的C编写而成,事实上,C编译器对任何计算机都适用。
  因为C语言就是为系统编程而设计,令人惊讶的是,在今天,C语言的一个主要用途是在嵌入式系统领域。与此同时,许多开发者选择这种轻便的、结构化的高级语言开发字处理程序,数据库和图形程序等等。
1.1  C的结构
  组成C程序的“积木”叫做函数,每个函数都有自己的用途,并且可以相互调用。每个函数包含可被执行的语句,而这些语句可以分组,从而形成语句块。做为程序员,你可以直接使用C标准库的的函数,也可以自己编写函数来实现既定目的。除此之外,还有很多专用的库可以使用,例如图形函数库。然而,使用这些非标准库,会限制了程序的可移植性,因为它必须运行在支持此类库的系统之上。
  每个C程序必须定义至少一个函数,而且均有一个名称为main()函数,此函数在程序开始运行时首先被调用,然后由它来调用子函数。
  Example 1-1为一个简单但完整的C程序,在本书中,我们将讨论声明、函数调用、输出流等细节,目前,我们仅关心普通C代码的结构,程序Example 1-1定义了两个函数:main()和circularArea(),main()函数调用circularArea()来计算一个指定半径升序的圆的面积,并使用标准库函数printf()将格式化后的结果输出在控制台上。
Example 1-1. 一个简单的C程序
1.    // circle.c: Calculate and print the areas of circles  
2.    #include                 // Preprocessor directive  
3.    double circularArea( double r );  // Function declaration (prototype form)  
4.    int main( )                        // Definition of main( ) begins  
5.    {  
6.      double radius = 1.0, area = 0.0;  
7.      printf( "    Areas of Circles/n/n" );  
8.      printf( "     Radius          Area/n"  
9.              "-------------------------/n" );  
10.    area = circularArea( radius );  
11.    printf( "%10.1f     %10.2f/n", radius, area );  
12.    radius = 5.0;  
13.    area = circularArea( radius );  
14.    printf( "%10.1f     %10.2f/n", radius, area );  
15.    return 0;  
16.  }  
17.   
18.  // The function circularArea( ) calculates the area of a circle  
19.  // Parameter:    The radius of the circle  
20.  // Return value: The area of the circle  
21.   
22.  double circularArea( double r )      // Definition of circularArea( ) begins  
23.  {  
24.    const double pi = 3.1415926536;    // Pi is a constant  
25.    return  pi * r * r;  
26.  }  
27.  Output:  
28.   
29.          Areas of Circles  
30.   
31.           Radius          Area  
32.      -------------------------  
33.             1.0           3.14  
34.             5.0          78.54  

在函数调用前,编译器要求每个函数首先需要声明,第3行的函数circularArea()的声明。标准库函数的声明在标准的头文件中,因为头文件stdio.h包含了函数printf()的声明,预处理指示符#include 声明的函数被预处理器间接地调用并插入到当前文件中。
  在程序中,可以任意安排函数定义的顺序,在Example 1-1中,可以将circularArea( )放在main()之前,这样的话,circularArea( )的原型声明就是多余的,因为函数的定义也是声明。
  函数的定义不能相互嵌套,你可以在一个函数体中定义一个本地变量,但不能定义一个本地函数。
1.1  源文件
  函数定义、全局声明、预处理符一起组成了C程序的源码,对于小程序,源代码写在一个文件中,大点的C程序包含多个源文件,由于函数定义通常依赖预处理符和全局声明,所以源文件通常有以下内部结构:
1.       预处理符
2.       全局声明
3.       函数定义
  C支持模块化编程,允许由多个文件组织一个程序,并且可以分别编辑、编译,互不影响。每个源文件通常包含逻辑上有关系的函数,并且通常由文件名来自说明此文件的内容。
Examples 1-21-3Example 1-1 中的程序相同,但是分开在两个源文件中定义。
Example 1-2. 第一个源文件,含有main()函数
1.    // circulararea.c: Calculates the areas of circles.  
2.    // Called by main( ) in circle.c  
3.      
4.    double circularArea( double r )  
5.    {  
6.      /* ... As in Example 1-1 ... */  
7.    }  

Example 1-3.  第二个源文件,含有 circularArea( ) 函数
1.    // circulararea.c: Calculates the areas of circles.  
2.    // Called by main( ) in circle.c  
3.      
4.    double circularArea( double r )  
5.    {  
6.      /* ... As in Example 1-1 ... */  
7.    }  

当一个程序含有多个源文件时,需要在多个文件中声明相同的函数和全局变量,定义相同的宏和常量,这些声明和定义因而形成了一种形式的文件头,或多或少地贯穿在程序中。为了简洁和一致,可以将这些信息写在一个独立的头文件中,然后在每个源文件中使用#include来引用,头文件通常由”文件名.h”来表示,一个头文件明确地包含在一个源文件中,也可能间接地包含在别的源文件中。
  每一个源文件及其包含的头文件组成了一个翻译单元,编译器顺序处理这些翻译单元,将源码解析为符号,符号为最小的语义单元,例如变量名称和操作符。
  任意个数的空白字符可以出现在两个连续的符号间,给你足够的自由来格式化源码,没有规则限制换行及缩进,你可以使用空格、TAB键、空白行来自由地来格式化源文件,只要方便阅读。而预处理语句则没有那么多的自由,一个预处理语句必须独立地出现在一行中,除过空格和TAB键,其它字符不能出现在#之前。
  关于代码格式,通常有好多不同的约定,但大多数包括下面的规则:
l  一个声明或语句占用一行;
l  使用缩进来体现语句块中的嵌套结构。
C核心技术手册(四)
1.1  注释
在源码中应该使用注释来说明程序,在C语言中,有两种插入注释的方法:块注释使用“/*”表示 开始,“*/”表示结束;行注释使用“//”开始,直到此行结尾。
  你可以使用分割符/**/在一行中开始和结束注释,并可以嵌套注释在多行中,例如,在下面的函数原型中,省略号()表示open()函数有第三个可选参数,注释解释此参数的使用方法:
[cpp] view plaincopyprint?
int open( const char *name, int mode, .../* int permissions */ );  
     你可以使用//来插入完整的一行注释,或者将代码和注释写于一行中,代码位于左,注释位于右边:
[cpp] view plaincopyprint?
const double pi = 3.1415926536;     // Pi is constant  
行注释方法在C99中才被正式纳入标准,但绝大多数编译器在c99之前就已经支持此类注释方法,尽管这种注释方法起源于C的前身BCPL语言中,但有时还会被称做C++风格的注释。
  在引号中,分割符表示一个字符常量或字符串常量,在这种场景下,/*//并不是开始一个注释,例如,下面的语句不包含注释:
[cpp] view plaincopyprint?
printf( "Comments in C begin with /*or //./n" );  
预处理器检查最后一个分割符来判断注释结束与否,所以不能在一注释中再嵌套一个块注释,但你可以在一个块注释中添加行注释。
[cpp] view plaincopyprint?
/* Temporarily removing two lines:
const double pi = 3.1415926536;     //Pi is constant
area = pi * r * r                   // Calculate the area
Temporarily removed up to here */  
如果想注释一段包含块注释的代码,你可以使用条件预处理语句。
[cpp] view plaincopyprint?
#if 0
  const double pi = 3.1415926536;    /* Pi is constant     */  
  area = pi * r * r                  /* Calculate the area */  
#endif  
  
1.1  字符集
  C将编译器编译源代码的环境(即:编译环境)和编译好的程序运行所依赖的环境(即:运行环境)区别对待,所以,C定义了两种类型的字符集,源文件中使用的称做源字符集,执行程序使用的称做执行时字符集。在一些C的实现的,两种类型是一样的,如果不同,编译器会将其转化为执行时字符集。
  每种字符集均包含基本字符集和扩展字符。C没有指定扩展字符,这些通常由本地语言来决定,扩展字符和基本字符集一起构成了扩展字符集。
  基本字符集和扩展字符集都包括下面的字符类型:
  拉丁字母:
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
a b c d e f g h i j k l m n o p q r s t u v w x y z
  数字
    0 1 2 3 4 5 6 7 8 9
   29个标点符号
! " # % &' ( ) * + , - . / : ; < = > ? [ / ] ^ _ { | } ~
   五种空白字符
空格、水平TAB,垂直TAB,新行,分页
基本的执行时字符集包四种非打印字符:
     Null: 表识字符串结束;
    警报(alter)
    退格(backspace)
    回车(carriage return)
  为了表示这些字符,需要以一个反斜杠(/)进行转义,
    /0 表示null;
    /a 表示警报(alter)
    /b 表示退格(backspace);
    /r 表示回车(carriagereturn)
  每个字符实际的值因程序不同而各异,C语言仅做以下规定:
l  基本字符集中的每个字符占一个字节;
l  字符Null为所有位均为0的一个字节;
l  十进制正数由小到大排列;
C核心技术手册(六)
1.1.1    宽字符和多字节字符
  C语言最初是在英文环境下发展而成,所以最初使用的是7位的ASCII编码字符集,其后,8位的字节变成最普遍的字符编码单位,但软件的国际化因素要求不能仅使用一个字节的字符编码方式,大量的不同与拉丁字母的多字节编码模式早已存在数十年,例如汉字、日文、韩文等。1994年,ISO C标准组织在“标准化附录1”中定义了两种大字符集:宽字符和多字节字符(一个字符可以使用一个或多个字节表示)。
  自从1994年的附录开始,C不仅提供char类型,而且还提供宽字节字符wchar_t,这种类型定义在头文件stddef.h中,此文件足够大,以至于可以表示任意扩展字符集。
  尽管C标准没有要求支持Unicode字符集,但一些软件为了支持宽字符而使用Unicode的转化格式UTF-16和UTF-32,Unicode标准与ISO/IEC10646标准一致,它是其他已存在字符集的超集,包括7位的ASCII字符。当Unicode标准执行时,wchar_t类型的宽度至少为16或32位,一个whchar_t类型的值表示一个Unicode字符,例如,下面的定义将变量wc初始化为希腊字母a。
  wchar_t wc = ‘/x3b1’;
编码顺序以/x开始表示在变量中以十六进制存储,这个值为小写的希腊字母首字母。
  在多字节字符集中,一个字符以一个或多个字节来编码,源字符集和执行时字符集均可能包含多字节字符。如果这样,基本字符集中的每个字符仅占用一个字节,除了null字符可能多个位全为0的字节表示外,没有多字节字符集。多字节字符可以被使用在字符常量、字符串、注释和文件名中,许多多字节字符集被设计用来支持某一种语言,例如日本工业标准字符集(JIS),由Unicode协会制定的UTF-8字符集,可以给分所有的Unicode字符,它使用1到4 个字节来表示一个字符。
  多字节字符和宽字符(即:wchar_t类型的字符)的主要区别在于:宽字符均为相同的大小,但多字节由变长的字节数来表示。这种表示使用多字节字符串被宽字节字符串处理起来要复杂的多,例如,字符’A’ 可以由单个字节表示。在一个多字节字符串中查找它比一个字节一个字节比较复杂,多字节字符适合保存文件内容。但是,C提供标准函数获取任意多字节字符的wchar_t值,并将任意的宽字符转换为它对应的多字节表示。例如,假设C编译器使用Unicode标准UTF-16和UTF-8,下面通过调用函数wctomb() (即:wide character to multibyte)来获取字母a的多字节表示。
35.  wchar_t wc = L'/x3B1';     a// Greek lower-case alpha,   
36.  char mbStr[10] = "";  
37.  int nBytes = 0;  
38.  nBytes = wctomb( mbStr, wc );  

  函数被调用后,数组mbStr中存储了多字节字符,值为”/Xce/Xb1”,函数wctomb()的返回值赋值给变量nBytes, 它为转化后多字节字符所占的字节数,即2。
此帖出自单片机论坛

最新回复

实在是太给力了~  详情 回复 发表于 2012-12-17 10:08
点赞 关注
 

回复
举报

6366

帖子

4914

TA的资源

版主

沙发
 
C核心技术手册(七)
目录(?)[+]
通用字符名
  C也支持通用字符名来使用扩展字符集,使用通用字符名,你可以指定任何扩展字符,它的Unicode值如下形式:
/uXXXX
  或者:
/UXXXXXXXX
其中,XXXXXXXXXXXX16进制的Unicode指针符号,使用小写字母u做为前缀,后面为四个十六进制数字,或者大写字母U,后面为8个十六进制数字. 如果前四个十六进制数字为0,则也可以写作/uXXXX或者/U0000XXXX
  通用字符名可以用来表示标识符、字符常量、字符串,但是,不能用来表示基本字符集中的字符。
  当你使用通用字符名来表示一个字符,编译器将它存储在字符集中,以便执行时使用。例如,如果执行时字符集在一个本地化程序中是ISO 8859-7 (8-bit Greek),则下面的的变量alpha使用/xE1初始化。
Char alpha = ‘/u03B1’
然而如果执行时字符集为UTF-16,需要定义一个宽字符变量:
Wchar_t alpha = ‘/u03b1’
在这种情况下,alpha的值为十六进制3B1,与通用字符名相同。
:不是所用编译器都支持通用字符名
1.5.3两字母词和三字母词
  C为标点符号提供了一种可选择的表示,因为标点符号并不是所有键盘上均可用,6个两字节,或2个字符记号,如下Table 1-1.
Table 1-1 两字母词
两字母词
等价表示
<:
[
:>
<%
{
%>
}
%;
#
%;%;
##
  如果这些序列出现在字符常量或字符串中,它们将不被认为是两字母词,在其他位置,它们的行为的确与单字符符号相同,例如,下面的代码段完全有等价,产生相同的输出。
两字母:
[cpp] view plaincopyprint?
int arr<::> = <% 10, 20, 30%>;  
printf( "The second array element is   
非两字母:
[cpp] view plaincopyprint?
int arr[ ] = { 10, 20, 30 };  
printf( "The second array elementis   
输出:
[cpp] view plaincopyprint?
The second array element is<20>.  
C也提供三字母词,即使用三个字符表示,它们均以两个问号开始,第三个字符决定三字母词所表示的标点符号,见Table 1-2.
Table 1-2.三字母词
??(
[
??)
??<
{
??>
}
??=
#
??/
/
??!
|
??’
^
??-
~
  三字母词允许仅使用ISO/IEC 646中定义的字符来写任何C语言程序,1991年的标准符合7位的ASCII,在编译的第一阶段,预编译器使用三个单独的等价字符替换三字母词。这也是就是说,不同与两字母词,三字母词在任何时候均可以被三个等价的单字符替代,包括中字符常量、字符串、注释和预处理语句中,例如,预处理器解释以下三字母词:
[c-sharp] view plaincopyprint?
printf("Cancel???(y/n) ");  
预处理器的输出如下:
[cpp] view plaincopyprint?
printf("Cancel?[y/n) ");  
如果你想使用三个字符中的一个,而不想被解释为三字母词,可以按以下方式书写:
C核心技术手册(八)
1.1  标识符
  标识符是指C程序中的变量、函数、宏、结构和其他对象的名称,标识符可以包括下面的字符:
l  基本字符集中的字母, a-z A-Z, 标识符是大小写敏感的;
l  下划线字符,_
l  数字,0-9,但首字符不能为数字;
l  通用字符用来表示其他语言中的字母和数字;
被允许的通用字符定义在C标准的附录D中,与ISO/IEC TR 10176标准中的字符相符。
  标识符中也允许使用多字节字符,但是要由C执行程序运行环境来决定哪些多字节字符可以使用。
  下面37个关键词为C语言保留,在编译器中有其他意义,不能被用做标识符:
Auto
Enum
Restrict
Unsigned
Break
Extern
Return
Void
Case
Float
Short
Volatile
Char
  For
Signed
While
Const
Goto
Sizeof
_Bool
Continue
  If
Static
_Complex
Default
Inline
Struct
_Imaginary
  Do
  Int
Switch
Double
Long
Typedef
Else
Register
  Union
  下面的例子是正确的标识符:
X  dollar  Break error_hander scale64
  下面的的例子是不正确的标识符:
1st_rank  switch  y/n x-ray
  如果编译器支持通用字符名,a则为有效的标识符,你可以将这它定义为一个变量名:
   double a = 0.5;
  你的代码编辑器将会以通用字符/u03B1表示a将其保存在文件中。
   当在程序中先择标识符时,记住有些标识符已经在C标准库中使用,所以不用使用它来为你的函数或全局变更命名,详见第15章。
  编译器提供了预指示符__func__, 你可以使用用此字符串常量来获得函数的名称,这对日志和调试输出很有帮助,例如:
[cpp] view plaincopyprint?
#include   
int test_func( char *s )  
{  
  if(s == NULL) {  
   fprintf( stderr,  
          "%s: received null pointer argument/n", _  _func_ _ );  
   return -1;  
}  
  /*... */  
}  
此例中,传一个空指针给函数test_func(),产生一个如下的错误信息:
[cpp] view plaincopyprint?
test_func: received null pointerargument  
  对于标识符的长度没有限制,但是,大多数编译器认为标识符中只有有限个数的字符才具有意义,换句话说,编译有可能区分不开均很长的标识符,根据C标准,编译器必须视函数名、全局变量名标识符的前31个字母为有意义的(即:外部链接标识符),并且认为其他的标识符至少前63个字符是有意义的。
C核心技术手册(九)
1.1.1    标识符的命名空间
  所有的标识符均可归属于以下四个分类之一,它们组成了命名空间:
l  标签名;
l  标记,用于识别结构体、联合体和枚举类型;
l  结构体或联合体的成员名,每个结构体或联合体为它的成员构成了一个独立的命名空间;
l  其他标识符,即普通标识符;
  属于不同命名空间中的相同标识符不会引起冲突,换句话说,你可以使用相同的标识符来标识不同的对象,只要它们属于不同种类,例如,编译器有能力区分一个变量和一个标签,尽管它们的名称相同。同样地,你也可以给一个结构类型、结构中的一个成员、一个变量取相同的名子,如下例子所示:
1.     struct pin { char pin[16];  /* ... */ };  
2.     _Bool check_pin( struct pin *pin )  
3.     {  
4.       int len = strlen( pin->pin );  
5.       /* ... */  
6.     }  

例子中程序的第1行定义了一个结构体类型,它的标签名为pin,包含一个名称pin的字符数组,在第2行,函数的参数pin是一个刚才定义的结构体类型的指针,第4行表达式pin->pin指出做为函数参数的指针所指的结构成员,标识符出现的上下文通常确定了它的命名空间,虽然如此,但在程序中明确地区分开各种标识符不失为一种很好的方法,这样在用户阅读时不会引起不必要的混淆。
C核心技术手册(十)
标识符范围
  标识符的范围即程序所能“看到”标识符的部分,范围的类型通常由你声明变量的地方决定(除过标签,它往往具有函数范围),下面是四种可能的范围:
  文件域:
         如果你在所有的程序块和参数列表这外声明了一个标识符,那么它将具有文件域,声明之后在任意地方使用它直到翻译单元结束。
       块域:
         除过标签,在块中声明的标识符具有块域,你只能在声明它的最小块域中使用它,最小块域通常为一个函数的函数体,在C99中,声明不必放在函数块的最前面,在一个函数中定义的参数名也具有块域,在函数体中有效。
       函数原型域:
           函数原型中的参数名具有函数原型域,因为这些参数名在原型外没有意义,它们往往用做注释,可被忽略。
       函数域:
           标签的范围通常为它所出现在函数块,即使它出现在嵌套的块中,换句话说,你可以使用goto语句跳到同一函数中任意具有此标签的地方(跳进一个嵌套的块中是个不好的主意)
          一个标识符的范围从它定义之后就开始,但是,结构体,标签,联合体、枚举类型和枚举常量是个例外,它们的范围在声明出现后即开始,所以它们可以在声明本身中再次引用,下面的为一个结构体类型的声明,最后一个成员为结构next,是一个刚声明的结构的指针:
[cpp] view plaincopyprint?
struct Node { /* ... */  
              struct Node *next; };          // Define a structure type  
void printNode( const struct Node*ptrNode); // Declare a function  
  
int printList( const struct Node *first )    // Begin a function definition  
{  
struct Node *ptr = first;  
  
while( ptr != NULL ) {  
   printNode( ptr );  
   ptr = ptr->next;  
}  
}  
  
在这个代码段中,标识符node,next, printNode printList均有文件域,参数ptrNode具有文件原型域,变量firstptr具有块域。
  在一个嵌套中,可以声明一个外部已经声明的标识符,,即使新标识符名称相同,如果你这样做,新标识符将有块或函数原型域,且块域或函数原型域将是一外层域的真子集,在这种场景下,内部标识符隐藏了外部的声明,所以外部声明的变量或函数在内部是不可见的,例如,下面的声明是请允许的:
[cpp] view plaincopyprint?
double x;               // Declare a variable x withfile scope  
long calc( double x );  // Declare a new x with function prototypescope  
  
int main( )
{  
long x = calc( 2.5 ); // Declare a long variable x with block scope  
  
  if(x < 0 )           // Here x refers tothe long variable  
  {float x = 0.0F;     // Declare a newfloat variable x with block scope  
   /*...*/  
}  
  x*= 2;               // Here x refers tothe long variable again  
/*...*/  
}  
在本例中,long类型的变量x声明在main()中,隐藏了double类型的变量x,因此,在main()中没有办法直接访问double类型的变量x,此外,if条件块中,float型变量x又隐藏了long类型的变量x
C核心技术手册(十一)
1.1 C编译器如何工作
  一旦你使用一个文本编辑器写了一个C源文件,你可能会调用一个C编译器将它翻译成机器码,编译器运转在一个包含有源文件和所有通过#include指示符引用进来的头文件的翻译单元中,如果编译器在翻译单元中没有发现错误,它将生成含有机器码的目标文件,目标文件常使用后缀.o或者.obj命名,另外,编译器可能也会生成一个汇编程序列表。
  目标文件也叫做模块,一个库,例如C标准库,包含已经编译的、可以立即使用的标准函数模块。
  编译器翻译C代码中的每一个翻译单元,每个包含任何头文件的源文件将生成一个独立的目标文件,然后编译器调用连接器,它会将目标文件、使用到的库函数合并成一个可执行程序,Figure 1-1描绘了由几个源文件和库经过编译、连接产生可执行文件的过程,可执行文件也包括目标操作系统加载它时所需要的信息。
   
1.7.1 C编译器翻译阶段
  编译过程涉及8个逻辑阶段,可能编译器会将有些阶段合并,但结果是一样的,这些阶段是:
1.        从源文件中读出字符,如果必要,将转换为源字符集中的字符。行尾指示符与换行符不同,将会被替换,同样的,任何三字母词将被替换为等价的单字符.(但两字母词不会被替换为等价的单字符。)
2.        当一个反斜杠后面紧跟一个换行符时,预处理器将会删除这两个字符;如果仅为一个换行符,将结束一个预处理指示,这个阶段允许你在行尾放置一个反斜杠来继续下一行,例如宏定义中常使用。
  :每一个非空文件,结尾必须有一个换行符
3.        源文件被分解为预处理记号和空白字符序列,每个注释会被当做一个空格处理。
4.        预处理完成,并做宏替换。
  :阶段一到阶段四应用于任何能过#includle指示符添加的文件,一旦编译器实现了预处理指示,它将会从源码的副本中删除。
5.        字符常量和字符串的中字符和序列将会被转换为执行时字符集中等价的字符。
6.        邻近的字符串被连接成一个单独的串。
7.        真正的编译开始执行,编译器分析符号序列,并生成相应的机器码。
8.        连接器解决涉及到的外部对象和函数,生成可执行程序,如果一个模块引用的外部对象或函数没有在任何翻译单元中定义,连接器将从标准库或其他的专用库中找出它们,外部对象和函数在一个程序中不能定义多次。
大多数编译器中,预处理器是一个独立的程序,或者编译器提供配置选项来仅做预处理(阶段一到阶段四为预编译处理序列),这种设置允许你验证你的预处理程序是否有预期的功能。
此帖出自单片机论坛
 
 

回复

6366

帖子

4914

TA的资源

版主

板凳
 
C核心技术手册(十二)
1.7.2 符号
  符号可以为任意一个关键字、标识符、常量、字符串、标点符号、函数、操作符、两字母词等符合语法规则的表达式,如下C语句报含5个符号:
printf("Hello, world./n");
单独的符号为:
   printf
    (
   "Hello, world./n"
    )
    ;
  符号被预编译器在翻译的第三阶段解释,与第七阶段编译器解释符号仅有一点不同:
l  对于#include指示符,预处理理器认可和”filename”两种模式;
l  在预处理阶段,字符常量和字符串不会由源字符集转化为执行时字符集;
l  与编译阶段不同,预处理器不区分整形常量和浮点型常量。
  在源文件转为符号阶段,编译器(或者预编译器)通常遵循下面的原则:每个连续的非空白字符必须附加在准备被读取的符号中,除非当添加后会使用一个有效的符号非法,这个规则解决了如下模糊的表达式:
a+++b
因为第一个+不是以a开头的标识符或关键词的一部分,它开始一个新的符号,第二个+附加在第一个之后形成一个有效的符号,但第却不能附加第三个,所以,这个表达式将被解释为:
  a++ + b
C核心技术手册(十三)
第一章    类型
  程序必须存储和处理不同种类的数据,如整型和浮点数,编译器需要知道给定的数值所代表的数据类型是什么。
  C中,术语”对象”为内存中的一块位置,它的内容代表值,对象也叫做变量,一个对象的类型决定了此对象在内存在所占有的空间大小和它可能的取值范围。例如:相同模式的比特位可以完全表示不同的整形,这取决于数据对象是否被解释为有符号数。
2.1 类型学
  C中的类型可以分为以下几类:
l  基础类型
标准和扩展整数类型
复杂浮点类型
l  枚举类型
l Void类型
l  派生类型
2  指针类型
2  数据类型
2  结构类型
2  联合类型
2  函数类型
  基本类型和枚举类型一起组成了算法类型,算法类型和指针类型一起被称做标量类型,最后,数组类型和结构类型构成了全体的类型(联合体因为在任何时间只有一个成员可以存储数据所以不被考虑)。
  函数类型描述了一个函数的接口,即,它指定了函数返回值的类型和所有参数的类型;
  其他的类型描述对象,这些描述也有可能包含对象存储的空间大小,如果包括,此类型完全可以称做对象类型;如果不包括,它将是一个不完整的类型。下面的数组变量的定义就是一个不完整类型的例子:
extern float fArr[ ];     //External declaration
fArr被声明为一个数据,它的元素类型为float。然而,因为数组的大小这里没有指定,fArr的类型是不完整的,只要全局数组fArr在另一个源文件中被指定大小,这个声明足够可以使你在当前的域内使用此数组。
  一些类型由多于一个关键词组成,例如unsigned short, 这种情形下,关键词可以以任意顺序书写,然而,关键词的顺序按惯例有个顺序,我们将在本书中使用。
C核心技术手册(十四)
整数类型
有五种带符号整数类型,其中多数可以由几个近义词指定,如下表Table 2-1
Table 2-1. Standard signed integer types
  
类型
同义词
Signed char
Int
Signed,signed int
Short
Short int, signed short,signed short int
Long
Long int, signed long, signed long int
Long long (C99)
Long long int, signed long long, signedlong long int
对于表Table 2-1中每一个有符号整形类型,存在个相应的占据同样大小内存的无符号类型,换句话说,如果编译器排列signed int对象在偶数字节的地址,那么unsigned int 对象也排列在偶数地址,这些无符号类型可参见下表Table 2-2.
Table 2-2. Unsigned standard integer types
类型
同义词
_Bool
Bool(define in stdbool.h)
Unsigned char
Unsigned int
Unsigned
Unsigned short
Unsigned short int
Unsigned long
Unsigned long int
Unsigned long long
Unsigned long long int
  C99引入无符号整形_Bool 来表示布尔值,其中true1false0,如果你在程序中包含了头文件stdbool.h,那么你也可以使用标识符bool, truefalse.这些对c++程序员来说很熟悉,宏bool _Bool类型的同义词,turefalse为常量10的象征符号。
Char类型也是整形的一种,但是,一个词的char类型有可能与signedchar等价,也有可能与unsignedchar相同,这取决于编译器,因为这在执行时才会被选择,char, signed charunsigned char是形式上不同的三种类型。
   注:如果你的程序使用char且取值小于0或大于127,你应该使用signedcharunsigned char来代替。
  你可以使用字符变量为编写算法,这取决于你的程序将char变量的值看做字符编码还是其他,例如,下面的代码段中视ch中的char值既为整形,又为一个字符,只是在不同的时刻:
[cpp] view plaincopyprint?
char ch = 'A';               // A variable with typechar.  
printf("The character %c has thecharacter code %d./n", ch, ch);  
for ( ; ch <= 'Z'; ++ch )  
printf("%2c", ch);  
printf()语句中,ch第一次被视为一个字符,且它的值被打印出来,接着打印出它的数值,同样地,在for循环中,指令++ch中,ch被视做整数,在函数printf()中又被看做字符,在系统中使用的是7-bitASCII编码,或者它的扩展,这段代码产生如下输出:
[cpp] view plaincopyprint?
The character A has the character code65.  
  A BC D E F G H I J K L M N O P Q R S T U V W X Y Z
Char类型通常占用一个字节,换句话说,sizeof(char)通常等于1,且至少是8位的宽度,基本字符集中的每一个字符均可使用一个char对象来表示其值。
  C仅定义其他类型的最小取值,short类型至少两个字节,long至少四个字节,long long至少八个字节,此外,虽然整形可能大于它的最小取值,大小必须遵循下面的顺序:
sizeof(short) sizeof(int) sizeof(long) sizeof(long long)
int 类型是最适合目标系统的整数类型,与CPU寄存器的大小和位格式相适应。
  整型的内部表示为二进制,有符号类型可能会使用符号和大小来表示,当做1的补码,或2的补码,最常用的是2的补码,带有符号的非负数的值取值在无符号类型的范围之内,同样,一个非负数的值也使用二进制表示,Table 2-3为对于有符号和无符号整型的位域表示。
Table 2-3. Binary representations of signedand unsigned 16-bit integers
  二进制
无符号十进制整型
有符号十进制整型(1补码)
有符号十进制整型(2的补码)
00000000 00000000
0
0
0
00000000 00000001
1
1
1
00000000 00000010
2
2
2
01111111 11111111
32,767
32,767
32,767
10000000 00000000
32,768
-32,767
-32,768
10000000 00000011
32769
-32,766
-32,767
11111111 11111110
65,534
-1
-2
11111111 11111111
65,535
-0
-1
Table 2-4列出标准整型的大小和取值范围
Table 2-4. Common storage sizes and valueranges of standard integer types
  类型
存储大小
最小值
最大值
Char
(same as either signed char or unsigned char)
Unsigned char
One byte
0
255
Signed char
One byte
-128
127
Int
Two bytes or four bytes
-32,768 or -2,147,483,648
32,767 or 2,147,483,647
Unsigned int
Two bytes or four bytes
0
65,535 or 2,147,483,647
Short
Two bytes
-32,768
32,768
Unsigned short
Two bytes
0
65535
Long
Four bytes
-2,147,483,648
2,147,483,647
Unsigned long
Four bytes
0
4,294,967,295
Long long(C99)
Eight bytes
-9,223,372,036, 854,775,808
9,223,372,036, 854,775,807
Unsigned long long (C99)
Eight bytes
0
18,446,744,073, 709,551,615
  下面的例子中,int型变量iIndexiLimit32位机器上占据4个字节:
   int iIndex,            // Definetwo int variables and
   iLimit = 1000;     // initializethe second one.
  要获得一个类型或变量的占用的空间大小,可以使用sizeof操作符,表达式sizeof(type) sizeof expression会计算出类型或对象的字节数,如果操作数是个表达式,那么计算出的大小将是此表达式类型的大小,在前面的例子中,sizeof(int)sizeof(iIndex)的大小相同,均为4,iIndex两边的圆括号可以忽略。
  你可以在头文件limits.h中找到你的C编译器中整型的范围,它们以宏的形式定义,如INT_MIN, INT_MAX,UINT_MAX等等。下面的例子Example 2-1中使用这些宏来显示charint的最小值和最大值。
Example 2-1. Value ranges of the types charand int
[cpp] view plaincopyprint?
// limits.c: Display the value ranges ofchar and int.  
//---------------------------------------------------  
#include   
#include      // Contains the macros CHAR_MIN, INT_MIN,etc.  
  
int main( )
{  
printf("Storage sizes and value ranges of the types char andint/n/n");  
printf("The type char is %s./n/n", CHAR_MIN < 0 ?"signed" :"unsigned");
  
printf(" Type   Size (inbytes)   Minimum         Maximum/n"  
        "---------------------------------------------------/n");  
printf(" char %8d %20d %15d/n", sizeof(char), CHAR_MIN,CHAR_MAX );  
printf(" int  %8d %20d%15d/n", sizeof(int), INT_MIN, INT_MAX );
return 0;  
}  
在整型的算法中,有可能发现溢出,当一个操作的结果不在类型的取值范围中时就会生溢出,在无符号整型的算法中,溢出被忽略,在数学术语中,一个无符号整型操作的有效结果等于它的值除以UTYPE_MAX+1的余数,这里的UTYPE_MAX为此无符号类型的最大取值,例如,下面的例子将会使变量的值溢出:
     unsigned int ui = UINT_MAX;
     ui += 2;                       //Result: 1
  C中仅提出了无符号整型的溢出现象,对于其他类型,溢出的结果是没有定义的,例如,溢出可能会被忽略,或者可能抛出一个信号,如果没有被扑获,将使用程序Abort.
C核心技术手册(十五)
具有精确宽度的整数类型
  整数类型的宽度定义为表示此整型值所使用的位的个数,包括符号位,典型的宽度有8163264位。例如,int类型的宽度至少为16位。
  C99中,头文件stdint.h定义了整数类型的宽度,这些类型列举在Table 2-5.中,其中以u开头的表示unsigned.下表中不是C99要求必须提供的使用”optional”标记。
                Table 2-5. Integer types withdefined width
类型
含义
执行
intN_t
uintN_t
宽度为N位的整型
optional
Int_leastN_t
Uint_leastN_t
宽度至少为N位的整形
要求N=8163264
Int_fastN_t
uint_fastN_t
宽度至少为N位的fast类型
要求N=8163264
Intmax_t
Uintmax_t
最大的宽度
要求
Intptr_t
Uintptr_t
宽度可以存储一个指针的整型
optional
例如,int_least64_tuint_least64_t都是宽度至少为64位的整型,如果可选的有符号类型(不带前缀u)被定义,相应地,也要求无符号类型(带有前缀u)也要求被定义,反之亦然。下面的例子中定义和初始化了一个元素为int_fast32_t类型的数据。
[cpp] view plaincopyprint?
#define ARR_SIZE 100  
int_fast32_t arr[ARR_SIZE];       // Define an array arr  
                                   // withelements of type int_fast32_t   
  
  for ( int i = 0; i < ARR_SIZE; ++i )
    arr = (int_fast32_t)i;     //Initialize each element  
Table 2-4中列举的类型通常为已存在标准库的同义词,例如,一个C编译器在stdint.h中有下面语句:
typedef signed char   int_fast8_t;
这个声明将int_fast8_t定义为等价的signed char.此外,有的实现也可能定义如int24_tuint_least128_t等扩展类型。
  有符号类型intN_t具有一个特性,它们必须使用2的补码来表示,所以,它们的最小值为-2,最大值为2N-1 1
Stdint.h中定义的类型的范围也很好获得,这个文件中也定义了获取最大值和最小值的宏,宏的名子使用大写字母,将类型中的_t命使用_MAX_MIN替代,例如,下例中使用最小值初始化了变量i64
int_least64_t i64 = INT_LEAST64_MIN;
头文件inttypes.h包含了stdint.h,它提供了其他的一些特性,例如扩展整型在函数printf()scanf()中的使用。
此帖出自单片机论坛
 
 
 

回复

6366

帖子

4914

TA的资源

版主

4
 
C核心技术手册(十六)
2.3 浮点类型
  C也支持特殊的数字类型,计算中使用的标准浮点类型(实数)如下所列:
l Folat
定义单精度变量
l Double
定义双精度变量
l Long double
定义扩展精度变量
  一个浮点值能被以有限的精度存储,这取决于表示它的二进制格式和存储它使用的内存的大小,精度以有效数的个数来表示,例如”精度为6个小数位”或”6位精度”的意思是此类型的二进制表示足够精确,可以存储一个具有6个小数位的实数。小数点的位置没有关系,并且小数据点前后的0不计算在这6个数中,数值123,456,0000.00123456都可存储在一个6位精度的类型中。
  C中,浮点型运算在内部被当做double或更高的精度类型处理,例如,下面的计算使用double类型。
[cpp] view plaincopyprint?
float height = 1.2345, width = 2.3456;  // Float variables have single  
                                       //precision.  
double area = height * width;           // The actual calculation is  
                                        //performed with double  
                                        // (orgreater) precision.  
如果将结果赋值给一个float型变量,它的值会被四舍五入,关于数据中的浮点型讨论,可参见15章的math.h
   关于浮点型的存储大小和二进制格式,C仅定义了最小的要求,但是,通常使用的格式是IEC1989年标准中定义的,通过查看宏__STDC_IEC_559__,可以判断C编译器是否支持IEC的浮点型标准。Table 2-6中列举了IEC60599中浮点型实数的取值范围和精度,使用十进法表示。
Table 2-6. Real floating-point type
Type
Storage size
Value range
Smallest positive value
Precision
float
4 bytes
±3.4E+38
1.2E-38
6 digits
double
8 bytes
±1.7E+308
2.3E-308
15 digits
long double
10 bytes
±1.1E+4932
3.4E-4932
19 digits
头文件float.h定义的宏允许你在程序中使用这些值或其他细节来表示实数的二进制表示。FLT_MIN,FLT_MAXFLT_DIG指出float 类型的取值范围和精度,相应地,doublelong double类型的宏以DBL_LDBL_为前缀。这类的宏及浮点数的二进制表示将在第15章的float.h中描述。
Example 2-2中的程序开始打印float型的一些典型值,接着,描述了将一个浮点数存放在float类型变量中四舍五入引起的错误。
Example 2-2. Illustrating the precision oftype float
[cpp] view plaincopyprint?
#include   
#include   
  
int main( )
{  
puts("/nCharacteristics of the type float/n");  
  
printf("Storage size: %d bytes/n"  
        "Smallest positive value: %E/n"  
        "Greatest positive value: %E/n"  
        "Precision: %d decimal digits/n",  
        sizeof(float), FLT_MIN, FLT_MAX, FLT_DIG);  
  
puts("/nAn example of float precision:/n");  
double d_var = 12345.6;       // Avariable of type double.  
float f_var = (float)d_var;   //Initializes the float  
                               // variablewith the value of d_var.  
printf("The floating-point number   "  
        "%18.10f/n", d_var);  
printf("has been stored in a variable/n"  
        "of type float as the value  "  
        "%18.10f/n", f_var);  
  printf("Therounding error is        "  
        "%18.10f/n", d_var - f_var);
  
return 0;  
}  
程序的最后一部分将输出以下内容:
[cpp] view plaincopyprint?
The floating-point number    12345.6000000000  
hasbeen stored in a variable  
oftype float as the value  12345.5996093750  
Therounding error is           0.0003906250  
在本例中,离十进制12,345.6最近的数为12,345.5996093750,这看起来不像是一个十进法的四舍五入,但在内部浮点型的二进制表示中,它的确是,而12,345.60则不是。
C核心技术手册(十七)
2.4 复数的浮点类型(C99)
  C99支持数学运算中的复数。1999年标准介绍了复数浮点类型及提供复数函数的扩展数学库,这些函数声明在complex.h中,包含例如三角函数csin(),ctan()等等。
  一个复数z可以在笛卡儿坐标系中使用z=x + y*i表示,其中xy为实数,i是一个虚构的单位,以以下等式定义:i2 = -1x 称做z的实数部分,y 称做z的虚数部分。
  C中,一个复数由一对浮点值来做为它的实数部分和虚数部分,两部分具有相同的数据类型,为floatdoublelong double,因此,有三类的复数浮点类型:
l Float _Complex
l Double _Complex
l Long double _Complex
这些类型中的每一个都具有相同的大小和队列(像数组一样有两个元素,类型为floatdoublelong double.
  头文件complex.h定义了宏 complexI,宏complex为关键词_Complex的近义词,宏I表示虚构单位i , 并有类型const float _Complex:
[cpp] view plaincopyprint?
#include   
// ...
2.5 枚举类型
  枚举为你在程序中定义的整型,枚举的定义以关键词enum开头,可能紧跟着一个标识符,且包含一系列可能的类型值,并且每一个均具有名子:
  enum [identifier] {enumerator-list};
下面的例子定义枚举类型enum color:
  enum color {black, red, green, yellow,blue, white = 7, gray};
标识符color 为枚举的标签,列表中black, red等标识符为枚举常量,类型为int,你可以使用这些常量,例如在switch-case语句中。
  枚举类型中的每一个枚举常量代表一个特定的值,它们可以由在列表中的位置隐式地表示出来,或者由一个常量初始化表达式显式指定。如果列表中枚举常量没有初始化,那么它的第一个常量的值就为0,后面的依次加1,所以在前面的例子中,列表中的值为0,1,2,3,4,7,8.
  在一个枚举类型的范围内,你可以在声明中使用它:
  enum color bgColor =blue,    //Definetwo variables
            fgColor = yellow;  // of type enum color.
  void setFgColor( enum color fgc);  //Declar a function witha parameter of type enum color.
  一个枚举类型通常符合标准整数类型之一,从而,编译器像处理通常的计算操作那样来处理枚举类型变量,编译器根据枚举常量的值来选择合适的整数类型。在前面的例子中,char类型用来表示enum color类型的所有值已经足够。
一个枚举中不同的常量可能具有同样的值:
  enum { OFF, ON, STOP = 0, GO = 1,CLOSE = 0, OPEN = 1};
正如前例所示,枚举类型的定义不一定非要一个标签,当你仅仅想定义常量,但不想声明此枚举类型的变量时,可以选择不定义标签。以枚举类型定义整型常量比使用一长串#define指示符定义要好,因为枚举给编译器提供了常量的名子,而不是一个数值,所以在调试时以名称来显示比数字方便了很多。
C核心技术手册(十九)
2.6 void类型
   类型void代表变量中没有值。因此,你不能使用此类型来声明变量或常量。在以下场景,可以使用void类型。
2.6.1 函数声明中的void
没有返回值的函数具有void类型,例如,标准函数perror()以如下形式声明:
void perror ( const char * );
函数参数列表中的void表示此函数没有参数:
FILE *tmpfile( void );
因此,当你试图做类似tmpfile(name.tmp)的函数调用时,编译器将报错。如果函数声明时参数列表中没有使用void,C编译器将不知道关于函数参数的任何信息,因此,可能判断不出函数调用是否正确。
2.6.2 void表达式
Void表达式即没有值的表达式,例如,没有返回值的函数调用语句就是一种:
[cpp] view plaincopyprint?
char filename[ ] ="memo.txt";  
(fopen( filename, "r" ) == NULL )
perror( filename );             // A void expression.  
  
转换操作符(void)expression明确地丢弃了表达式的值,例如一个函数的返回值:
(void)printf("I don't need this function's return value!/n");
2.6.3 void指针
  一个void类型的指针表示一个对象的地址,但它没有类型。你可以使用此无类型的指针来声明函数,因为它可以操作各种类型的指针参数,或返回一个”多用途”的指针,标准的内存管理函数是一个简单的例子:
   void *malloc( size_t size );
   void *realloc( void *ptr, size_t size );
   void free( void *ptr );
Example 2-3所示,你可以将一个void指针值赋给别一个对象的指针,反之亦然。不带有明确的类型转换。
Example 2-3. Using the type void
[cpp] view plaincopyprint?
// usingvoid.c: Demonstrates uses of thetype void  
//-------------------------------------------------------  
#include   
#include   
#include   // Provides the following functionprototypes:  
                     // void srand( unsignedint seed );  
                     // int rand( void );  
                     // void *malloc( size_tsize );  
                     // void free( void *ptr);  
                     // void exit( int status);  
  
enum { ARR_LEN = 100 };  
  
int main( )
{  
  inti,                                //Obtain some storage space.  
     *pNumbers = malloc(ARR_LEN * sizeof(int));  
  
  if (pNumbers == NULL )  
{  
   fprintf(stderr, "Insufficient memory./n");  
   exit(1);  
}  
  
srand( (unsigned)time(NULL) );       // Initialize the  
                                        //random number generator.  
  
  for( i=0; i < ARR_LEN; ++i )  
   pNumbers = rand( ) % 10000;         // Store some random numbers.  
  
printf("/n%d random numbers between 0 and 9999:/n", ARR_LEN);  
  for( i=0; i < ARR_LEN; ++i )         //Output loop:  
{  
   printf("%6d", pNumbers);        // Print one number perloop iteration  
   if ( i % 10 == 9 ) putchar('/n');  // and a newline after every 10 numbers.
}  
free( pNumbers );                    // Release the storage space.  
return 0;  
}  
C核心技术手册(二十)
第三章    常量
  C代码中,常量是表示一个固定值的记号,它可能是整型、浮点型、字符、或一个串。一个常量的类型由它的值和记法来决定。
  这里讨论的常与混合常量不同,混合常量在C99标准中介绍,它通常是可修改的对象,类似于变量,了解混合常量的详细介绍和特殊操作可参见第五章。
1.1    整型常量
  一个整型常量可以以普通的十进制数表示,或者是八进制或十六进制,总之你必须通过前缀指定一个类型。
  一个十进制常量以一个非0的数字开始,例如,255
  一个以前导0开始的数值被解释为八进制,八进制(8为基数)仅使用数字07。例如,047是一个合法的八进制常量,表示4 * 8 + 7,它与十进制常量39等价。十进制常量255与八进制常量0377 相等。
  十六进制常量以前缀0x0X开始,十六进制数字AF可以使用大写或小写。例如,0xff,0Xff,0xFF,这些均表示同一个十六进制常量,其值与十进制常量255相等。
  因为你定义的整型常量最终将被用于表达式和声明中,它们的类型很重要,常量的类型在它的值被定义的同时已经确定下来,例子中的整型通常具有int类型。然而,如果一个整型的值超出int类型的范围,这时,它必须使用一个更大的类型,既然这样,编译器在分配它时会有层次地首先使用一个足够大的类型来表示此值。例如,十进制常量类型层次如下:
Int, long, long long
对于八进制和十六进制常量,其类型层次是:
Int, unsigned int, long, unsigned long, long long, unsigned long long
例如,在一个16位的系统上,整型常量50000使用的类型是long,因为此系统上int的最大值为32,767,或者215 1
你也可以在程序中显式地使用前缀来改变常量的类型,一个带有前缀lL具有类型long(如果有必要,会使用更大的类型,与刚才提及到的层次一致),同样地,带有前缀llLL的常量最少具有类型long long,前缀uU可能用来保证常量具有一个无符号类型。前缀longunsigned可以进行组合,Table 3-1给出了一些例子。
Table 3-1. Examples of constants withsuffixes
  
整型常量
类型
0x200
int
512U
unsigned int
0L
long
0Xf0fUL
unsigned long
0777LL
long long
0xAAAllu
unsigned long long
C核心技术手册(二十一)
3.2    浮点常量
浮点常量可被写为十进制或十六进制,在下面两个小节描述。
3.2.1   十进制浮点常量
  一个普通的浮点常量由一个十进制数字包含一个小数点的序列组成。在科学计数法中,你也可以给它的值乘以10的幂。10的幂可以使用指数表示,引入字母eE,一个包含指数的浮点常量不需要包含小数点,Table 3-2给出了一些十进制浮点常量的例子。
Table 3-2. Examples of decimalfloating-point constants
浮点常量
10.0
10
2.34E5
2.34 x 105
67e-12
67.0 x 10-12
小数点可能是第一个或最后一个字符,因此,10..234E5都允许使用的数,然而,不带小数点的数字10将是一个整型常量,而非浮点型常量。
浮点常量的默认类型为double,你也可以通过增加前缀Ff指定一个常量为float,或者使用前缀Ll使常量的类型为long long,如下例子所示:
Float f_var = 123.456F; //初始化
Long double ld_var = f_var * 987E7L;
3.2.2   十六进制浮点常量(C99)
  C99标准引入了十六进制浮点常量,与十进制浮点数相比它具有一个关键的优势,如果你使用十六进制来指定一个常量的值,在计算机中它将精确地以二进制浮点格式存储,没有误差。
  十六进制浮点常量包含前缀0x0X, 及一个十六进制数字序列并带有一个可选的小数点(在此场景下,我们应该称它为十六进制小数点),和一个2的指数,其中指数是一个十进制数,使用字母pP表示。例如,常量0xa.fP-10与十进制数(10 + 15/16) x 2-10 (not 2-16)相等,还有如下一些等价的表示方法,0xA.Fp-10,0x5.78p-9, 0xAFp-14 0x.02B3p0
  在十六进制浮点常量中,必须包含一个指数,即使它的值为0,这一步是为了区分前缀F(在指数后)和十六进制数字F(在指数左边),例如,如果指数没有被要求,常量0x1.0F可能被解释为float型的1.0,也可能被解释为double类型的1+15/256
  与十进制浮点数类似,十六进制浮点数也具有默认的类型double,增加一个前缀fF使用一个常量的类型变为float,或者使用前缀lL使常量类型变为long double
此帖出自单片机论坛
 
 
 

回复

6366

帖子

4914

TA的资源

版主

5
 
C核心技术手册(二十二)
3.3    字符常量
  字符常量由一个单引号中包含一个或多个字符组成。一些例子如下:
a’   ‘XY’ ‘0’    ‘*’
所有源字符集中的字符均可用于字符常量中,除去单引号(),反斜杠(/),及换行符,可以使用转义字符来表示这些字符:
‘/’’        ‘//’   ‘/n’
在字符常量中所有需要转义的字符将在”转义字符”一节描述。
3.3.1   字符常量的类型
  字符常量具有int类型,除非它们被明确地使用前缀L定义为宽字符(wchart_t),如果一个字符常量仅含有占有一个字节的字符,它的值将是执行时字符集中的字符码。例如,常量aASCII编码中的十进制值为97,含有多个字符的字符常量其值因编译器的不同而各异。
下面的代码段测试输入的值是否在15之间(包括15):
   #include
   int c = 0;
    /*... */
    c= getchar( );                          //Read a character.
   if ( c != EOF && c > '0' && c < '6' )   // Compare input to character
                                            //constants.
    {
     /* This block is executed if the user entered a digit from 1 to 5. */
}
如果char类型是有符号的,那么字符常量的值也可能为负数,因为常量的值是charint类型转换的结果。例如,ISO 8859-1,或称ANSI字符集,通常使用8-bit字符集,在它中,英磅的表示字符£,其十六进制值为A3:
   int c = '/xA3';                        // Symbol for pounds sterling
   printf("Character: %c    Code: %d/n", c, c);
如果执行时字符集为ISO8859-1,且类型char是有符号的,那么上面例子中的printf语句将产生如下输出:
  Character: £     Code: -93
在一个不使用单字节字符的程序中,你可以使用宽字符常量,宽字符常量具有类型wchar_t,使用前缀L,如下面这些例子:
  L'a'   L'12'   L'/012'  L'/u03B2'
包含单个多字节字符的宽字符常量的值为标准函数mbtowc()的返回值。
3.3.2   转义字符
  转义字符以反斜杠/开始,表示一个单字符,转义字符允许你表示任意字符常量和字符串常量,包括不可打印字符和具有特殊意义的字符,例如’和”, Table 3-3列出了C中公认的转义字符。
Table 3-3. Escape sequences
转义字符
字符值
输出设备上的动作
/’
单引号()
可打印字符
/’’
双引号()
/?
问号(?)
//
反斜杠(/)
/a
警报
产生一个听得见或可见的信号
/b
退格
从当前位置向前移动一个字符
/f
换页
从当前位置移动到下一个新页的开始
/n
换行
从当前位置移动到下一个新行的开始
/r
回车
从当前位置移动到当前行的开始
/t
水平TAB
从当前位置移动一个水平tab
/v
垂直TAB
从当前位置移动一个垂直tab
/o,/oo, /ooo
八进制字符
可打印字符
/xh[h…]
十六进制字符
/uhhhh
/Uhhhhhhhh
通用字符名
如表Table 3.3所示,通用字符名也可使用转义字符,通用字符允许指定扩展字符集中任意的字符,而不管使用的是何种编码方式。
你可使用八进制或十六进制转义字符来表示在类型unsigned char的取值范围内的任意字符码,或者在类型wchar_t取值范围内的任意宽字符码。如Table 3-4.
Table 3-4. Examples of octal andhexadecimal escape sequences
八进制
十六进制
描述
/0’
/x0’
null
/033’  ‘/33’
/x1B’
ESC
/376’
/xfe’
254
/417’
/x10f’
非法,其值超过unsigned char 的取值范围
L’/417’
L’/x10f’
宽字符常量,类型为wchar_t
-
L’/xF82’
宽字符常量
表中没有最后一个常量L/xF82’对应的八进制表示,因为八进制转义字符不能容纳三个八进制数字,同样,宽字符常量L/3702’包含两个字符,分别为L370’和L2’。
C核心技术手册(二十三)
3.4  字符串常量
字符串常量包含一个字符序列(也包括空格),均位于一个双引号中,例如:
Helloworld!/n”
与字符常量一样,字符串常量可以包含源字符集中的所有字符,唯一例外就是使用双引号,反斜杠(/)、换行符等需要通过转义来表示,下面printf语句中首先产生一个警告音,接着,在双引号中指定一个目录,使用%s来替代指针doc_path的地址:
Char doc_path[128] = “.//share//doc”;
Printf(“/aSee the documentation in the directory /”%s/”/n”, doc_path”);
字符串常量是一个静态char数组,它包含以字符串结束符null/0)结束的字符码。空串””在内存中占用一个字节,因为它包含了null字符。不能用一字节存储的字符使用多字节来存储。
如前面所有的例子,你可以使用一个字符串来初始化一个字符数组,也可以用它来初始化一个char指针:
Char *pStr = “Hello, world!”;  //pStr points to the first character, ‘H’
在这种初始化中,字符串常量表示每一个元素的地址,这跟数组名是一样的。
Example 3-1中,数据error_msg包含3char指针,每一个赋值一个字符串首字母的地址。
Example 3-1. Sample function error_exit( )
#include
#include
Void error_exit(unsigned int error_n)
{
Char *error_msg[] = {“Unknown error code./n”,
                   “Insufficient memory./n”,
                   “Illegal memory access./n”};
Unsigned int arr_len =sizeof(error_msg)/sizeof(char *);
If (error_n > = arr_len)
Error_n = 0;
Fput(error_msg[error_n], stderr);
Exit(1);
}
与宽字符常量一样,你可以使用前缀L在字符串常量中使用宽字符:
L”Here`s a wide-string literal.”
宽字符串常量定义一个以null结束的数组,它的元素类型为wchar_t,数组通过转换多字节为宽字符来初始化,这与标准函数mbstowcs()一样。同样地,使用转义表示的任何通用字符名以单个的宽字符存储。
在下面的例子中,/u03b1表示通用字符awprintf()printf函数的宽字符版本。用来格式化和打印一个宽字符串:
Doubel angle_alpha = 90.0/3;
Wprintf(L”Angel /u03b1 measures %lf degress./n”, angle_alpha);
字符串常量中的任何多字节字符或转义字符均不能在执行时字符集中表示,它们的值取决于编译器。
编译预处理器连接任何以空格隔开的单个相邻字符,如下所示,这种连接使为了字符串易于阅读而分成多行变得容易了:
#define PRG_NAME “EasyLine”
Char msg[] = “The installation of ” PRG_NAME
            “is now complete.”;
如果连接的任一字符串是宽字符常量,刚连接后的结果串也为一个宽字符串。
另一种分隔一个字符串为多行的方法是使用反斜杠结束,如下面的例子:
Char info[] =
  “This is a string literal broken up into /
Several source code lines./nNow one moreline:/n/
That`s enough, the string ends here.”;
字符串将在新行的开始继续,任何在左页边的空格,例如several前的空格,均是字符串的一部分,此外,此串包含了两个换行符,一个在Now前,另一个在that`s之前。
编译器解释转义字符在连接相邻串之前,所以,下面来自一个宽字符串的两个字符串常量以’/xA7’和’2’开始:
L”/xA7” L”2 et cetera”
然而,如果这个串写在一起,如L/xA72 et cetera,则串中每个字符为宽字符’/xA72’。
虽然C没有严格禁止修改字符串常量,你也不应该尝试这样做,在下例中,第二个语句就是尝试替换字符串是首字符:
Char *p = “house”; //Initialize a pointer to char
  *p= ‘m’;        //This is not a good idea!
这个语句不被禁止,但在有些系统上会引起运行时错误,首先,编译器当做一个常量去处理字符串,可能将它置于只读内存,所以试图进行写操作将会引起错误,其次,如果两个或更多的相同串在程序中使用,编译器会将它们存储在相同的位置,所以修改可能会引起另外一个使用它的地方出现异常。
  然而,当你使用一个字符串来初始化数组变量,你可以修改数组内容:
Char s[] = “house”                //Initialize an array of char
S[0] = ‘m’                                 //Now thearray contains the string “mouse”.
C核心技术手册(二十四)
第四章    类型转换
C中,不同类型的操作可以化合成一个操作,例如,下面的表述式:
Double dVar = 2.5 //Define dVar as a variable of type double.
dVar *= 3;       //Multiply dVarby an integer constant.
  If( dVar < 10L )   //Compare dVar with along-integer constant
  {/* … */}
当操作数据具有不同的类型时,确定的场景下,在执行操作前,编译器会尝试将它们转换来统一的类型,而且,你必须在程序中插入类型转换指令,类型转换产生的结果具有新的类型,可能是void类型(表示表达式的值被丢弃),或都是数学精英或指针,例如,结构体指针可能会被转化为不同的指针类型,然而,一个实际的结构体值不能被转换为不同的结构体类型。
  当操作数的类型使用不恰当时,编译器使用一个隐式的类型转换,或者调用函数时传入一个与形参类型不匹配的参数,在变量初始化或者赋值时,编译器在必要时也会执行隐式转换。
  你也可以显式地使用cast操作符进行类型转换。
  (type_name) expression
在下面的例子中,cast操作符将两个整形相除的结果转换为浮点型:
   Intsum = 22, count = 5;
  Double mean = (double)sum/count;
因为cast操作符的优先级高于除法,所以在例子中,变量sum先被转换为double类型,然后编译器隐式地将除数count转换为相同的类型。然后才进行除法操作。
  当可能发生信息丢失时,你要尽可能地使用cast操作,就像int转换为unsigned int,例如,显式的转换避免了编译器告警,例如,将一个函数的返回值使用void转换丢弃,这样会使用你可能也丢弃了错误指示,但这却没有告警。
本章中会举例说时编译器提供的隐式转换,然而,却很少有使用cast操作符的例子,除非很必要。
C核心技术手册(二十五)
4.1 算数类型转换
  类型转换通常存在于任意两个算数类型中,当必要时,编译器隐式地执行它们,如果新类型能够表示它,此种转换将保护它的值,这通常也并不绝对,例如,当你将一个负数转换为无符号类型,或将一个浮点数的小数部分由double转换为int时,新类型不能表示原来的值,在这种情况下,编译器会产生一个告警。
4.1.1 类型的层次
  当算术操作数具有不同的类型时,隐式类型转换由类型的层次来决定,类型的层次依据下列规则:
l  任意两个无符号整数类型具有不同的转换等级,如果一个比另一个宽,它将具有较高等级;
l  每一个有符号整数类型具有与相应的无符号整数类型相同的转换等级,charsigned charunsigned char 具有相同的转换等级;
l  标准的整数类型等级排列如下:
_Bool < char < short < int
l  任意标准整数类型具有比同样宽度的扩展整数类型较高的等级;
l  每一个枚举类型具有与相应的整数类型相同的等级;
l  浮点类型以下下顺序排列:
Float < double < long double
l  等级最低的浮点型float的等级高于任何整数类型;
l  每一个复数浮点类型具有与其实数类型和虚数类型相同的等级。
4.1.2整数提升
  在任何表达式中,你通常可以使用一个其类型等级低于int的值来替代int unsigned int类型操作数,你也可以将位域当做整数类型操作数来使用,在这种情况下,编译器使用整数提升,任何类型级别低于int的半自动被转换为int类型,倘若int类型能够表示所有操作数原来的类型;如果int不足以表达,操作数会被转换为unsigned int
  整数提升通常保护操作数的值,一些例子如下:
  Char c = ‘?’;
  Unsigned short var = 100;
   If(c < ‘A’)          //The characterconstant ‘A’ has type int; the value
                    //of c is implicitlypromoted to int for the comparison.
  Var = var + 1;      //Before theaddition, the value of var is promoted to int
                   // or unsigned int.
在最后这个语句中,在执行加法操作前,编译器将第一个加数的类型提升为intunsigned int,如果intshort具有相同的宽度,就像在16位机器上,这时,带符号的int类型没有足够的宽度来表示unsigned short类型var的所有值,此种场景下,变量的值被提升为unsigned int,在加法执行以后,结果转换为unsigned short
C核心技术手册(二十六)
4.1.3常用算术转换
  常用的算术类型转换为隐式转换,常用算术类型转换目标是为所有的操作数即操作结果寻找一个通用的类型。
常用的算术类型转换涉及以下操作数:
l  具有两个操作数的算术操作符: *, /, %, +-
l  关系操作符: <, <=, >, >=, ==!=
l  位操作符:&, |^
l  条件操作符:?:
除关系操作符外,常用算术转换获得的公共实数类型通常为结果值的类型,然而,如果一个或多个操作数为复数类型,刚结果也具有复数类型。
常用算术转换的应用如下:
1.        如果任一个操作数为复数,此时,具有低转换等级的操作数转换为具有与其他操作数相同的类型,实数和虚数部分各自进行转换;
换句话说,如果一个操作数具有复数类型,通常算术转换仅匹配实数部分,下面是一些例子:
   #include
   // ...
   short n = -10;
   double x = 0.5, y = 0.0;
   float _Complex f_z = 2.0F + 3.0F * I;
   double _Complex d_z = 0.0;
   y  = n * x;           // The value of n is converted totype double.
   d_z = f_z + x;        // Only thevalue of f_z is converted to
                          // double _Complex.
                          // The result of theoperation also has type
                          // double _Complex.
   f_z = f_z / 3;        // Theconstant value 3 is converted to float.
   d_z = d_z - f_z;      // The valueof f_z is converted to the type
                          // double _Complex.
2.        如果两个操作数为整数,则先对两个操作数进行整型提升,在此之后,如果类型还不相同,这时,会以以下规则进行转换:
a)        如果一个操作数具有unsigned类型T,且它的转换级别等于或高于其他操作数的类型,那么其它操作数将转换为类型T
b)        否则,如果一个操作数具有signed类型T, 且其转换级别高于其它操作数类型,如果类型T足够大能够之前的所有类型值;如果不是,则两个操作数转换为类型T对应的无符号类型。
下面是一些例子:
  IntI = -1;
Unsigned int limit = 200U;
Long n = 30L;
  If( I < limit)
   X= limit * n;
在此例中,if条件中为比较语句, i的值为-1, 首先会转换为unsigned int,结果是一个很大的正数,在32位系统上,其值为232 1, 且在任何系统上,其值都大于limit,所以if条件为false.
例子中的最后一行,如果long的取值范围能够包含unsigned int的所有取值,则limit的值转换为变量n的类型long,如果不是,例如,intlong均为32位的宽度,则它们的类型将转换为unsigned long
除了以一情景外,常用算术转换会保护操作数的值:
l  当一个巨大的整数转换为浮点类型时,目标类型的精度可能不足以精确地表示此数;
l  超出无符号类型取值范围的负数;
在这两种情景中,数值超过了目标类型的范围或精度,这种转换将在接下来的一节”算术类型转换的结果”中讲述。
4.1.4 其他的隐式类型转换
在下列场景下,编译器也会自动转换算术值:
l  在分配或初始化,右操作数的值通常会转换为左操作数的类型;
l  在函数调用中,实参将转换为相应的形参类型,如果参数没有被声明,则会进行默认的参数提升,整型参数进行整型提升,float参数提升为double;
l  return语句中,return表达式的值将转换为函数返回值的类型。
在一个复数的赋值语句中,例如x+= 2.5, 两个操作数的值首先进行算术转换,所以运算的结果类型就为左操作数的类型,下面是一些例子:
   #include        //Declares the function double sqrt( double ).
   int   i = 7;
   float x = 0.5;    // The constantvalue is converted from double to float.
    i= x;           // The value of x isconverted from float to int.
    x+= 2.5;        // Before the addition,the value of x is converted to
                   // double. Afterward, thesum is converted to float for
                   // assignment to x.
    x= sqrt( i );      // Calculate the squareroot of i:
                   // The argument is converted from int todouble; the return
                   // value is converted fromdouble to float for assignment to x.
   long my_func( )
    {
     /* ... */
     return 0;      // The constant 0is converted to long, the function's return
                   // type.
}
C核心技术手册(二十七)
4.1.5 算术类型转换结果
  由于不同的类型有不同的目的、特性和局限性,将一个类型转换为另一类型通常要处理这些差异,通常,一个类型转换后的值取决于目标类型的特性。
4.1.5.1 _Bool类型转换
  任何标量类型可被转换为_Bool, 结果为0i.e. 当标量值为o时,使用false; 当标量值为1时或非0时,使用true; 因为一个null指针比喻为0,所以转换为_Bool后,其值为false.
4.1.5.2 无符号整型转换
  当整型值在新的无符号类型范围内时,它的值会被保护,换句话说,如果它们在0Utype_Max之前,Utype_MAX为无符号类型的最大取值。
  在无符号类型取值范围之外的值,它们的值将会多次加上或减去Utype_MAX+ 1,直到其值在新类型的取值范围之内,下面的例子描述了把一个负数赋值给无符号整型:
   #include        //Defines the macros USHRT_MAX, UINT_MAX, etc.
   unsigned short  n = 1000; // Thevalue 1000 is within the range of unsigned
                              // short;
    n= -1;                   // the value -1must be converted.
将有符号数-1赋值给一个无符号类型的变量,程序隐式地加上了USHRT_MAX + 1直到其结果在新类型的取值范围内,因为-1 + USHRT_MAX + 1 = USHRT_MAX, 上例中,n的最终结果为USHRT_MAX;
对正整数而言,会减去(Utype_MAX+ 1)使其值在新类型的取值范围之内,这与除以(Utype_MAX + 1)效果相同,如下例所描述:
   #include        //Defines the macros USHRT_MAX, UINT_MAX, etc.
   unsigned short  n = 0;
    n= 0xFEDCBA;             // The value is beyond the range of unsigned
                              // short.
如果unsignedshort16位的宽度,那么它的最大值为USHRT_MAX,其十六进制为FFFF,当值FEDCBA被转换为unsigned short时,如果与它除以十六进制10000(USHRT_MAX + 1)的余数相等,通常小于或等于FFFF,在此例中,n的值最终被赋于DCBA
将一个浮点型实数转换为无符号或有符号整数类型,编译器将丢弃分数部分,如果整数部分的值在新类型的取值范围外,结果将会转换为无符号类型,例如:
   double x = 2.9;
   unsigned long n = x;            // The fractional part of x is simply lost.
   unsigned long m = round(x);     // If x is non-negative, this has the
                                     // same effect as m = x + 0.5;
本例中初始化变量n时,x的值由double转换为unsignedlong,并丢弃分数部分0.9.整数部分的 2将被赋值给n, 在初始化m时,使用C99标准中的round()函数,计算与x最接近的整型值,并返回一个double类型的值,小数部分的0.3在赋值给unsigned long类型的m时会被丢弃。
当一个复数转换为无符整数类型时,虚数部分首先会被丢弃,转换后的结果值为浮点型,例如:
   #include         // Defines macros such as UINT_MAX.
   #include        // Defines macros such as the imaginary
                                // constant I.
   unsigned int  n = 0;
   float _Complex  z = -1.7 + 2.0 *I;
    n= z;                      // In this case, the effectis the same as
                                // n = -1;
                                // Theresulting value of n is UINT_MAX.
变量z的虚数部分被丢弃,留下实数部分的浮点值-1.7, 然后浮点型的小数部分也被丢弃,留下的整型值为-1,它通过加上UINT_MAXT + 1 转换为unsigned int类型,因此,最后n被赋值为UINT_MAX
此帖出自单片机论坛
 
 
 

回复

6366

帖子

4914

TA的资源

版主

6
 
C核心技术手册(二十八)
4.1.5.3 有符号整型转换
  整型类型转换中超过目标类型取值范围的问题,不仅存在在无符类型转换中,也存在有符号类型转换中,例如,当一个值由类型longunsigned int转换为int,结果为有符号整型并溢出,这与转换为无符号整数不同,后者将会丢弃执行结果。
  绝大多数编译会丢弃原来值二进制表示的高位,并将最低位解释为新类型,如下例所示,在这种策略下,一个unsigned int类型现有的位模式被解释为一个有符号int值。
   #include         // Defines macros such as UINT_MAX
   int i = UINT_MAX;           //Result: i = -1 (in two's complement
                                //representation)
然而,由于编译器不同,这样的一个转换有的编译器会抛出一个信号提示值溢出。当一个实数和一个复数转换为有符号整型时,与转换为无符号整型具有同样的规则,这与前面一节描述的一样。
4.1.5.4 浮点类型实数转换
  不是所有的整型值可以被浮点型表示,例如,虽然float类型的取值范围包含了longlong long的取值范围,float仅精确到小数点后6位,因而,一起long类型的值不能精确地存储在float对象中,这种转换的结果是下一个或更高的二进制值,如下例所示:
   long  l_var = 123456789L;
   float f_var = l_var;           //Implicitly converts long value to float.
   printf("The rounding error (f_var - l_var) is %f/n", f_var -l_var);
记住此例中的减操作,像所有浮点型一样,结果至少具有double类型的精度,此代码段输出如下:
The rounding error (f_var - l_var;) is3.000000
任意一个浮点类型可以使用精度比它大的另一个浮点类型来表示,因此,当一个double值转换为long double,或者当一个float值转换为doublelong double,值会精确地保持。当从一个高精度转换为一个低精度类型时,然而,其值可能会超出新类型的范围,如果超出,转换的结果是未定义的;如果其值在目标类型的范围内,但目标类型的精度不能精确地表示它,此时,结果为下一更小的或下一个更大的二进制值,Example 2-2中的程序描述了转换为一个低精度浮点类型时产生的误差错误。
  当一个复数转换为一个实数时,虚数部分被简单地丢弃掉,结果为复数据的实数部分,它之后可能会转换为本章中描述的目标类型。
4.1.5.5 浮点型复数转换
  当一个整型或浮点型实数转换为一个复数类型时,结果值的实数部分按照前面的规则转换为浮点型实数,虚数部分为0
  当一个复数转换为另一个不同的复数类型时,实数和虚数数分单独根据浮点型实数的规则进行转换。
   #include        // Defines macros such as the imaginary
                                // constant I
   double _Complex dz = 2;
   float _Complex fz = dz + I;
在两行初始化语句中,整型常量2隐式地转换为double_Complex并赋值给dz, dz的结果为2.0 + 0.0 * I
fz的初始化中,dzdouble_Complex值两部分都转换为floatfz的实数部分等于2.0F, 虚数部分为1.0F
C核心技术手册(二十九)
4.2 非算术类型转换
  指针和数组名,还有函数名也遵循隐式和显式类型转换,结构体和联合体不能转换,虽然它们的指导可以转换为其他的指针类型。
4.2.1数组和函数操作指示符
  一个数组或函数操作指示符为具有类型的任何表达式,在大多数情况下,编译器隐式地转换一个数组的类型,及数组的名子转为数组首元素的指针,数组表达式在以下情景下不能转换为指针:
l  当使用sizeof操作符操作数组时;
l  当使用&操作符时;
l  当使用字符串变量初始化charwchar_t数组时;
下面的例子示范了隐式转换数组操作符为指针,使用%p打印指针值:
   #include
   int *iPtr = 0;                      // A pointer to int, initializedwith 0.
   int iArray[ ] = { 0, 10, 20 };      // An array of int, initialized.
  int array_length = sizeof(iArray) / sizeof(int); // The number ofelements:
                                                    // in this case, 3.
   printf("The array starts at the address %p./n", iArray);
    *iArray = 5;                     // Equivalent to iArray[0] = 5;
    iPtr = iArray + array_length - 1; // Point to the last element ofiArray:
                                      // Equivalent to
                                      // iPtr =&iArray[array_length-1];
printf("The last element of the array is%d./n", *iPtr);
在初始化array_length时,表达式sizeof(iArray)取得了数组的长度,而不是指针的大小,然而,在其他三个语句中,指示符iArray隐式地转换为一个指针,如下:
l  在第一次调用printf();
l  在使用操作符*;
l  iPtr赋值时;
字符数组的名子在字符串操作中也当做指针使用,如下例:
   #include
   #include            // Declares size_t strlen( const char *s )
    char msg[80] = "I'm a string literal.";    // Initialize an array of char.
    printf("The string is %d characterslong./n", strlen(msg));
                                              // Answer: 21.
   printf("The array named msg is %d bytes long./n",sizeof(msg));
                                              // Answer: 80.
本例中strlen(msg)调用中,标识符msg隐式地转换为指向数组首元素的指针,类型为函数的入参类型constchar *, strlen()仅仅统计字符开始到第一个null字符出现时所有的字符个数。
  类似地,任何表示函数的表达式,如函数名,也可以隐式地转换为指向函数的指针,同样,当使用地址操作符&时,将不会使用这种转换,sizeof操作符不能使用在函数类型的操作中。下面的例子描述了函数名隐式转换为指针,程序初始化了指向函数的指针,函数的调用在一个循环中。
   #include
   void func0( ) { puts("This is the function func0( ). ");}  // Two functions.
   void func1( ) { puts("This is the function func1( ). "); }
   /* ... */
   void (*funcTable[2])(void) = { func0, func1 }; // Array of two pointersto
                                                   // functionsreturning void.
   for ( int i = 0; i < 2; ++i )  // Use the loop counter as the array index.
      funcTable( );
C核心技术手册(三十)
4.2.2 显式指针转换
  将一个指针转换为另一个指针类型,必须使用一个显示转换,一些情景下,编译器会提供隐式的转换,这些描述在本章后面的部分,指针也可以显式地转换为整型,反之亦然。
4.2.2.1 对象指针
  你可以显示地转换一个对象指针为另一个对象指针类型。在程序中,你必须确保这种转换有意义。例如:
   float  f_var = 1.5F;
   long *l_ptr = (long *)&f_var;    // Initialize a pointer to long with
                                      // theaddress of f_var.
   double *d_ptr = (double *)l_ptr; // Initialize a pointer to double with
                                      // thesame address.
   // On a system where sizeof(float) equals sizeof(long):
   printf( "The %d bytes that represent %f, in hexadecimal: 0x%lX/n",
           sizeof(f_var), f_var, *l_ptr );
   // Using a converted pointer in an assignment can cause trouble:
   /*  *d_ptr = 2.5;  */   //Don't try this! f_var's location doesn't
                            // have space for adouble value!
    *(float *)d_ptr = 2.5;  // OK: stores a float value in that location.
  如果转换后的对象指针没有对齐要求,使用指针的结果将没有定义,在另一个场景中,将指针的值再次转换为它原来的类型,会产生一个与原指针等价的指针。
  如果将对象指针转换为字符类型(char, signed char unsigned char), 结果将为此对象首字节的指针,首字节被认为是二进制的低地址,不管系统的字节顺序,下例使用此特性打印一个结构体变量的十六进制值。
   #include
   struct Data {
                  short id;
                  double val;
                };
   struct Data myData = { 0x123, 77.7 };          // Initialize a structure.
   unsigned char *cp = (unsigned char *)&myData;  // Pointer to the first
                                                  // byte of the structure.
   printf( "%p: ", cp );                          // Print the starting
                                                  // address.
   for ( int i = 0; i < sizeof(myData); ++i )     // Print each byte of the
     printf( "%02X ", *(cp + i) );                // structure, in hexadecimal.
   putchar( '/n' );
本例输出如下的结果:
0xbffffd70: 23 01 00 00 00 00 00 00 CD CC CC CC CC 6C 53 40
结果的前两个字节为2301,表示此代码是在一个小端的系统上执行的,在myData结构中字节最低位地址为shoart类型的变量id
4.2.2.2 函数指针
  函数通常具有返回值类型,也包含参数类型,你可以将一个函数指针的类型转换为另一个函数指针类型,在下例中,typedef语句为一个具有double类型参数和double类型返回值的函数定义了一个类型名。
   #include                   // Declares sqrt( ) and pow( ).
   typedef double (func_t)(double);   // Define a type named func_t.
   func_t *pFunc = sqrt;              // A pointer to func_t, initialized with
                                        // theaddress of sqrt( ).
   double y = pFunc( 2.0 );           // A correct function call by pointer.
   printf( "The square root of 2 is %f./n", y );
   pFunc = (func_t *)pow;             // Change the pointer's value to the
                                        //address of pow( ).
   /*  y = pFunc( 2.0 );  */          // Don't try this: pow( ) takestwo
                                        //arguments.
在本例中,函数指针pFunc被赋值为不同类型函数的地址,然而,如果程序使用指针调用函数时,如果与原来的指针类型不匹配,程序的结果将不可预计。
C核心技术手册(三十一)
4.2.3 隐式指针转换
  编译器隐式地转换确定的指针类型,分配、条件表达式使用操作符==!=,函数调用使用三种隐式的指针转换,如下节所描述,三种隐式指针转换:
l  一个指针对象类型可以隐式地转换为void指针,反之亦然;
l  一个给定类型的指针可以隐式地转换为一个该类型更合格的版本;
l  一个null指针常可以隐式地转换为任意指针类型。
4.2.3.1 指针转换为void
Void指针为具有void *类型的指针,通常称为多用途指针,可以表示任意对象的地址,而不关心它的类型,例如,malloc()函数返回一个void指针,在你使用内存块前,void指针必须转换为指向一个对象的指针。
Example 4-1示范了void指针的多个用途,程序使用标准函数qsort() 对数组进行排序,此函数定义在头文件stlib.h中,其原型如下:
     void qsort( void *array, size_t n, size_t element_size,
                int (*compare)(const void *,const void *) );
qsort()以升序排列数组元素,以array地址开始,使用快速排序算法,假定函数具有n的元素,其大小为element_size
  4个参数compareqsort()调用的用做排序的函数指针,用来比较的两个元素的地址通过此函数指针的参数传入,通常,比较函数由程序定义,其返回值必须大于0,或小于0,或等于0,以表示第一个元素大于、或小于、或等于第二个元素。
Example 4-1. A comparison function forqsort( )
#include
#define ARR_LEN 20
/*
* Afunction to compare any two float elements,
*for use as a call-back function by qsort( ).
*Arguments are passed by pointer.
*
*Returns: -1 if the first is less than the second;
*          0 if the elements are equal;
*          1 if the first is greater than the second.
*/
int floatcmp( const void* p1, const void* p2 )
{
float x = *(float *)p1,
       y = *(float *)p2;
return (x < y) ? -1 : ((x == y) ? 0 : 1);
}
/*
*The main( ) function sorts an array of float.
*/
int main( )
{
  /*Allocate space for the array dynamically: */
float *pNumbers = malloc( ARR_LEN * sizeof(float) );
  /*... Handle errors, initialize array elements ... */
  /*Sort the array: */
qsort( pNumbers, ARR_LEN, sizeof(float), floatcmp );
  /*... Work with the sorted array ... */
  return 0;
}
在上面的例子中,malloc()函数返回一个void *,接着,在qsort()中对pNumbers赋值时被隐式地转换为float*,第一个参数隐式地由float*转换为void*,函数名floatcmp隐式地解释为函数指针,最终,当floatcmp()qsort()调用时,它接收void*类型的参数,在初始化float变量前必须显式地转换为float*类型。
C核心技术手册(三十二)
4.2.3.2指针转换为限定的对象类型
  C中,限定类型有const, volatilerestrict,例如,编译器隐式地转换任意指针为int,需要使用const int指针,如果你要删除掉限定词,就需要使用显式类型转换,如下例所描述:
   int n = 77;
   const int *ciPtr = 0;   // Apointer to const int.
                            // The pointeritself is not constant!
   ciPtr = &n;          //Implicitly converts the address to the type
                         // const int *.
    n= *ciPtr + 3;      // OK: this has thesame effect as n = n + 3;
   *ciPtr *= 2;         // Error: youcan't change an object referenced by
                         // a pointer to constint.
   *(int *)ciPtr *= 2;  // OK:Explicitly converts the pointer into a
                         // pointer to anonconstant int.
本例中倒数第二行语句描述了为什么指向const类型的指针有时叫做只读指针,尽管你可以修改指针的值,但不能修改它们所指的对象。
4.2.3.3 Null指针常量
Null指针常量为一个值为0的整型常量,或者是一个表示值0void指针,NULL宏定义在头文件stdlib.h中,表示一个null指针常量,下例描述了使用NULL宏初始化指针。
   #include
   long *lPtr = NULL;      //Initialize to NULL: pointer is not ready for use.
   /* ... operations here may assign lPtr an object address ... */
   if ( lPtr != NULL )
    {
      /* ... use lPtr only if it has been changedfrom NULL ... */
    }
当你将null常量指针转换为另一个指针类型,结果称为null指针,null指针的位模式不需要一定为0,然而,当你比较null指针和0,或NULL、或另一个null指针,结果通常为truenull指针与指向有效类型或函数的指针比较时,通常结果为false
C核心技术手册(三十三)
4.2.4 指针和整型之前的转换
  你可以显式地将指针转换为整数类型,反之亦然。此种转换的结果由编译器决定,且与编译器所运行的系统的地址结构有关。指针和整数类型的转换在系统编程中很有用,当程序访问物理地址(例如:ROMI/O寄存器等)时也会使用。
  当你将指针转换为一个取值范围没有指针所表示值大的整数类型时,结果是不可定义的,相反地,转换一个整数为指针类型没有必要必须产生一个有效指针,例如:
   float x = 1.5F, *fPtr = &x;                 // A float, and a pointer toit.
   unsigned int adr_val = (unsigned int)fPtr;  // Save the pointer value
                                               // as an integer.
   /*
    * On an Intel x86 PC in DOS, the BIOS data block begins at the
    * address 0x0040:0000.
    * (Compile using DOS's "large" memory model.)
    */
   unsigned short *biosPtr = (unsigned short *) 0x400000L;
   unsigned short com1_io = *biosPtr; // The first word contains the
                                        //  I/O address of COM1.
   printf( "COM1 has the I/O base address %Xh./n", com1_io );
最后三条语句从系统配置表中读取出硬件信息,假设操作环境允许程序访问内存区,在一个大内存模式下编译的DOS程序中,指针具有32位宽度,在高16位中包含一个段地址和一人低16位的偏移量(通常以segment:offset的形式表示),因而前的biosPtr指针可以使用一个long int型常量初始化
此帖出自单片机论坛
 
 
 

回复

6366

帖子

4914

TA的资源

版主

7
 
C核心技术手册(三十四)
第五章    表达式和操作符
  一个表达式包括常量序列、标识符和操作符。程序中表达式的目的可能为了取得结果值,或赋值。
  一个常量、字符串,或对象或函数的标识符均是一个表达式,复杂的表达式会使用括号,被称做主要表达式。
  每个表达式都有一个类型,即为表达式结果值的类型,如果表达式不产生结果,它将具有void类型,下面是一些例子(假设变量a的类型为int, 变量z的类型为float_Complex)。
Table 5-1. Example expressions
Expression
Type
'/n'
int
a + 1
int
a + 1.0
double
a < 77.7
int
A stingliteral.”
char *
abort()
voied
sqrt(2.0)
double
z / sqrt(2.0)
double _Complex
从上表可以看出,组合表达式会对操作数使用到操作符,且操作数可能为主要表达式或组合表达式,例如,在乘法中,你可以把一个函数调用当作因数,同样地,函数的参数也可以使用多个操作符,如下例:
2.0 * Sin(3.14159 * fAngleDegrees / 180.0)
5.1 表达式的求值
在我们考虑操作符的细节前,本节提供了少许的原则,将会帮助你理解C中表达式是如何求值的,在分解组合表达式时,操作符的优先级尤为重要。但序列点和左值也同样要了解C语言如何工作。
5.1.1 左值
  左值为一个指明对象的表达式,最简单的例了是变量名,首字母L在术语中的意思是 left,因为一个左值指明一个对象,它可以出现在赋值操作符的左边。即:leftexpression= rightexpression.其他没有指明对象的表达式被称做右值,即,右值为一个表达式中,出现在赋值操作符右边,例如,常量和算术表达式。
一个左值通常可以理解为相应对象的地址,除过位域变量、或者使用register定义的变量。操作符产生一个左值包括[]*.下面是一些例子。
Table 5-2. Pointer and array expressions maybe lvalues
Expression
Lvalue?
array[1]
Yes, an array element is an object with alocation
&array[1]
No;the location of the object is not anobject with a location
ptr
Yes;the pointer variable is an object witha loction
*ptr
Yes; what the pointer points to is also anobject with a location
ptr + 1
No; the addition yields a new addressvalue, but not an object
*ptr + 1
No; the addition yields a new arithmeticvalue, but not an object
如果一个对象声明为常量,此时它将不能在表达式的左边使用,即使它是一个左值,如下例描述:
Inta = 1;
Const int b = 2, *ptr = &a;
b =20;   //Error: b is declared as constint.
*ptr= 10; // Error:ptr is declared as a pointer to const int.
在本例中,表达式abptr*ptr均为左值,然而,b*ptr为常量左值,因为ptr声明为指向const int的指针,你不能使用它来修改所指的对象,详细描述见第11章。
  一个赋值语句的左操作数,像自增(++)和自减(--)操作符,不仅仅为一个左值,也为一个可修改的左值,可修改的左值不能使用const类型声明,且不为数组类型。如果一个可修改的左值指明一个结构或联合类型的对象,它的成员必须声明,直接或间接地,具有const限定类型。
C核心技术手册(三十五)
第一部分的暂停一段时间再翻译,为了方便编译和调试,先把第三部分讲了,呵呵。
C核心技术手册(三十六)
第三部分 基础工具
第十八章 GCC的使用
  本章介绍如何使用GCCC的源代码编译为可执行程序,首先,我们介绍GCC基本的选项及编译的步骤,接着我们介绍如何使用GCC的警告选项来检查程序,最后,我们总结编译优化的选项。
  本章提供最基本的GCC知识给你,如果更细节的知识,如体系结构的细节或系统的特殊选项,这些基本的方向将告诉你如何在GCC手册中查找你想要的。手册在GCC发布版本中以文本的形式包含。你也可以查看HTML格式的文档,路径为:http://gcc.gnu.org/onlinedocs/
18.1 GNU编译器
  GCC以前为“GNU C Compiler”的缩写,自从它出现以后,就开始支持除过C之外的其他编程语言,包括C++, Ada, Object-C,FortranJava, 所以GCC的含义被重新定义为”GNU Compiler Collection”,此编译器合并了好多特性来翻译不同的语言,在本书中,我们仅关心和C相关的特性。
  GCC为一个多目标的编译器,换句话讲,它有可互换的模块来为许多不同的计算机体系结构产生输出,就像组合式概念所建议的,GCC也可以被用做交叉编译,就是说,你可以生成与GCC运行的操作系统不同的系统上的执行程序,然而,这需要进行特殊的配置,而且绝大多数GCC适合生成于它们所运行系统相同系统上的执行程序。
  GCC不仅支持许多C的“方言”版本,而且能够区分它们,换句话说,你可以使用命令行参数来控制在编译你的程序时,依据哪种C语言标准,对所有C99的特性支持还没有全部完成,GCC的开发者们维护了一个目前支持C99特性的列表,参见http://gcc.gnu.org/c99status.html
18.2 获取和安装GCC
  如果你有一个类UNIX系统,很可能已经安装了GCC,为了找出它,可以在命令提示符下输入 cc version来查看,如果GCC已经安装,且使用默认的C编译器名称cc,你将会看到编译的版本号及版权信息:
[duanyx@localhost root]$ cc --version
cc (GCC) 3.4.6 20060404 (Red Hat 3.4.6-3)
Copyright (C) 2006 Free SoftwareFoundation, Inc.
This is free software; see the source forcopying conditions.  There is NO
warranty; not even for MERCHANTABILITY orFITNESS FOR A PARTICULAR PURPOSE.
如果GCC可能已经安装,但是名子不是cc,以防万一,试着使用其固有的名称为调用编译器,如下:
[duanyx@localhost root]$ gcc –version
  如果GCC没有安装,请教你的系统卖主看是否GCC在系统的二进制安装包中,否则,你可以从自由软件基金获取GCC源码,然后在你的系统上编译它,源码路径:http://gcc.gnu.org/mirrors.html,参考http://gcc.gnu.org/install/.上的指导一步步进行,如果你仅想支持C的编译,这时你仅需要GCC的“核心发行版本”,它将去除其他语言的模块,其大小仅为GCC软件包大小的一半。
  如果你的系统中根本没有C编译器,那就不能编译GCC源码,你需要安装一个编译的二进制,GCC的网站上维护了一个使用第三方编译器编译好的GCC安装包,并支持从AIXWindows的不同系统。
18.3 GCC编译C程序
  当运行GCC时,它的默认行为是由源代码生成可执行程序,举一个简单的例子,我们使用GCC来编译本书第一个例子Example1-1,如下:
# gcc -Wall circle.c
这个命令行仅含有一个编译名、源文件名和一个选项:-Wall,用来指导GCC在程序中有警告存在时将警告信息打印出来, 可在本章的后面的“编译器警告”一节得到更多信息。如果源文件中没有错误,GCC运行并退出,不会打印任何信息。它的输出为在当前路径下产生一个程序文件,采用默认名称a.out(在Windows中,默认的名称为a.exe, 我们可以运行这个程序:
# ./a.out
它将产生如下的输出:
[root@localhost gcc_test]# ./a.out
   Areas of Circles
    Radius          Area
-------------------------
      1.0           3.14
      5.0          78.54
如果不想让可执行程序名称为a.out,可以使用 o 选项来为输出文件指定一个文件名:
# gcc -Wall -o circle circle.c
此命令产生相同的执行程序,但现在的名子为circle
C核心技术手册(三十七)
18.3.1 进阶
  下面的小节介绍GCC的选项,使用你可以控制编译过程的每个阶段:预处理、编译、装配和连接。你也可以执行单独的步骤来调用单独的工具,例如C预处理器cpp,汇编程序as,
连接器ldGCC也可被配置来使用指定主机上的外部程序,为了统一,本章描述如何执行这四步来控制程序。
18.3.1.1 预处理
  在将程序向实际的编译器提交前,预处理器执行指示符和扩展源代码中的宏,GCC通常不会保留预处理阶段的结果,但是你可以使用-E选项将预处理阶段输出保存下来以便以后诊断程序时使用。此选项将使用GCC在预处理完成后停止。预处理器直接将结果输出在标准输出设备上。除非你使用-o选项指定一个输出文件名。
# gcc -E -o circle.i circle.c
因为头文件比较大,预处理器输出文件通常包含通常不用的标题。
你可能会出现-c 选项很有用,因为它可以使预处理器移除注释和头文件:
# gcc -E -C -o circle.i circle.c
下列选项通常用来影响GCC在预处理阶段的行为:
-D name[= definition]
  在预处理源代码之前定义符号name,name不能定义在它们自己的源文件或头文件中。将这个选项目与指示符#ifdef name一起使用来控制编译。
-U name
Name将为未定义的符号,如果它被定义在命令行或GCC的默认设置中,当它出现在命令行中时,-D-U选项用来处理其。
-I directory[: directory[…]]
  当使用#include包含头文件后,在除系统标准的include目录外的指定目录下寻找它们。
通常的查找顺序为:
1.        源文件所在目录(在#include中使用引号””)
2.        在命令行中使用-I选项指定的目录
3.        在环境变量C_INCLUDE_PATHCPATH中指定的目录
4.        系统默认的include目录
-I-
  此选项将命令行中-I directory选项分为两组,所有附加在-I-左边的目录不会被查找,例如:
#include
  做为替代,他们仅查找在#include中使用引号包含的头文件,因此:
#include “myheader.h”
  第二组由-I-右边的按指示符组成,无论任何#include指示符,这些目录将被查找。
  此外,如果-I-出现在命令行中,则包含源文件的目录将不再被做为第一个被用来查找头文件。
18.3.1.2 编译
  编译器的核心工作就是将C程序翻译成汇编语言,汇编语言是接近真实机器代码的易于人理解的程序设计语言,因此,每个CPU体系架构具有不同的汇编语言。
  通常GCC将汇编语言的输出保存在一个临时文件中,当汇编程序运行时立刻删除它们,但你可以使用-s选项,使编译程序在产生汇编输出后停止,如果不指定一个输出文件名,使用-s选项的GCC将默认使用.s为后缀的文件来存储汇编文件。例如:
$gcc –S circle.c
编译器预编译circle.c并将其翻译成汇编语言,将结果存储在circle.s中,如果要在结果文件中和注释一样包含C变量的名子,需要使用附加选项 fverbose-asm;
$ gcc -S -fverbose-asm circle.c
18.3.1.3 装配
  因为每种机器体系结构均有自己特有的汇编语言,GCC调用它宿言主系统上的汇编程序将汇编语言翻译成可执行的二进制代码,结果为一个object文件,它包含了源文件中定义的各函数对应的机器码,并包含一个描述所有具有外部连接的对象的符号表(symbol table)。
  如果你使用GCC在一个命令行中完成编译和连接, 这时object 文件是临时的,当连接器运行时它将被删除。然而大多数情况下,编译连接是独立的,-c选项目可以指示GCC不要连接程序,但是为每一个输入文件生成一个以源文件名命令的,后缀为.o的文件。
$ gcc -c circle.c
此命令生成一个object文件circle.o
可以使用-Wa选项来将命令选项目传递给汇编程序,例如,假设我们想让汇编器带下面的参数运行:
-as=circle.sym
它将以一个单独的列表将模块符号表打印出来,并保存在一个以源文件名命令的,后缀为.sym的文件中。
-L
包括本地符号表,即具有内部连接的C标识符。
我们可以在GCC的参数中使用-Wa来带上汇编器的参数,如下:
$ gcc -v -o circle -Wa,-as=circle.sym,-Lcircle.c
命令列表中-Wa后必须有一个逗号,具不能包含空格。-v选项让GCC打印出编译时第一步使用的参数,允许你看到汇编命令行的结果。
可以在汇编器的-a选中增加几个开关来控制列表的输出,详细信息可以查看汇编器的手册, 如果使用没带参数的-a选项目,将会产生默认的列表输出,它包含汇编代码和符号表。
GCC的选项目可以使用编译器在输出文件中包含调试信息,如果附加-g选项,生成的汇编结果将包含C源代码的行信息。
$ gcc -g -o circle -Wa,-a=circle.list,-Lcircle.c
结果列表文件为circle.list,允许你按行检查编译器如何进行C语句的翻译。
C核心技术手册(三十八)
18.3.1.4 连接
  连接器将多个二进制的object文件连接成为一个可执行文件,在进程中,它完成使用外部引用来替换程序中本地对象的引用。连接器依据汇编器提供的符号表来完成这些工作。
  此外,连接器必须为程序中使用的C标准库的函数添加代码,在连接的上下文中,一个库就是一系列object文件的集合,为了便于处理,它们以归档文件的形式被集合在一个独立的文件中。
  大多数标准函数库通常在文件libc.a(其中后缀.a代表“archive),或者在一个共享的动态连接库libc.so( 其中后缀.so代表“shared object)。这些库通常在/lib/ /usr/lib中,或者在GCC默认的其他库目录中存放。
  特定的函数存放在各自己的库文件中,比如标准库中的浮点数函数。为了示范如何连接这些库,让我们使用其他的变量来替代circle.cp的定义,在Example1-1中,变量pi使用一个常量来初始化:
const double pi = 3.1415926536;    // Pi is a constant
我们可以使用反正切函数来替换,如下:
const double pi = 4.0 * atan(1.0);   // because tan(pi/4) = 1
当然我们要在源文件的头部添加指示符#include ,但函数atan() 没有在源代码中定义,也不在libc.a中,编译此circle.c文件,我们要使用-l选项来连接math库。
$ gcc -o circle -lm circle.c
math库的文件名为libm.a(在支持动态库的系统 上,GCC自动使用共享动态库libm.so,如果它可用,可以查看后面的“动态连接与共享object文件”一节来获取更多信息),其中前缀lib和或缀.a均为标准命名要求,当在命令行中基本名称跟随参数-l时,GCC自动扩展它们,例如m
通常,GCC在标准库所在目录中根据库文件名称来自动查找,例如/usr/lib。有三种连接一个库的方式,其中之一是给GCC提供全路径和库文件名,就像object文件一样,例如,假设库文件名为libmath.a,存放在/usr/local/lib中,如下的命令将使用GCC编译circle.c,然后连接器将结果文件circle.olibmath.a进行连接:
$ gcc -o circle circle.c/usr/local/lib/libmath.a
本例中,库的名子必须放置在使用它的源文件或object文件之后,这是因为连接器在命令中顺序地使用这些文件,并且不会返回到一个前面的库文件来解决一个后面对象中的引用。
第二种方不在GCC默认路径中连接的方式是使用-L选项来添加另一个库的目录来让GCC查找:
$ gcc -o circle -L/usr/local/lib -lmathcircle.c
你可以使用多个-L选项来添加多个库目录,或者使用一个-L选项,后跟随一个目录列表;第三种方式是确保你所引用库的路径在环境变量LIBRARY_PATH中。
你可以直接传递选项参数到连接阶段,使用-Wl选项,后跟一个使用逗号分隔的列表,如下:
$ gcc -lm -Wl,-M circle.c circulararea.c> circle.map
命令行中的选项-Wl传递-M给连接器,指示连接器在标准输出上打印一个连接和一个内存印象文件。
-Wl后的列表必须以逗号开始,且不包含空格,如果不确定,你可认在同一个GCC命令行中使用多个-Wl选项,使用-v选项来查看连接器的结果。
C核心技术手册(三十九)
18.3.1.5 所有输出
  GCC有另外一个选项-save-temps,可以便利地一次性获得所有中间输文件。当你使用此选时,GCC正常地进行编译和连接,但将会在当前目录下保存预处理的输出、汇编语句、object文件。使用-save-temps选项生成的文件名与源文件名相同,对于预处理输出、汇编语句、object文件,后缀分别为.i.s.o
18.3.1.6 无一输出
  如果使用-fsyntax-only选项,GCC将不会进行预处理、编译、装配、或连接,它仅仅测试输入文件的语法,具体可参见本章后面的“编译警告”一节。
18.3.2 多个输入文件
  在第一章中,我们将circle.c分成两个文件,编译多个源文件将会产生多个object文件,每个文件中均包含机器码和对应源文件中各对象的符号,GCC为对象输出使用临时文件,除非你使用-c选项指示它仅编译,不进行连接:
$ gcc -c circle.c
$ gcc -c circulararea.c
这此命令将在当前工作目录下产生两个object文件,文件名分别为:circle.ocirculararea.o,你也可以将源文件名写在同一个GCC命令行中,获得同样的结果:
$ gcc -c circle.c circulararea.c
  实际上,编译器在同一时间仅执行一个小任务,大程序往往包含许多源文件,在开发期间它们将被编译,测试、编辑、再编译多次,但整个代码的变动很小,为了节省时间,像make这样的控制构建的工具产生,它们使编译器仅对object文件比其对应的最新的源文件老的文件进行重新编译。
一次将当前源文件编译后产生的object文件进行连接可以使用下面命令:
$ gcc -o circle circle.o circulararea.o –lm
GCC假设以.o为后缀的文件为要进行连接的object文件。
18.3.2.1 文件类型
  编译器认识一系列C程序的扩展文件,如下:
.c
  C源码,在编译前进行预处理。
.i
  预处理输出,为编译做准备。
.h
  C头文件。
.s
汇编语言文件。
.S
带有C预处理指示符的汇编语言文件,在装配前进行预处理。
GCC也支持其他一些文件扩展:
.ii.cc.cp.cxx.cpp.CPP.c++.C.hh,  .H, .m .mi, .f, .for, .FOR, .F, .Fpp, .FPP,.r, .ads.adb
这些文件类型涉及C++,Objective-C, Fortan, Ada, 带有其他扩展名的文件将被当做object文件来进行连接。
如果你为输入文件使用其他的命名,可以使用-x file_type选项来指定GCC处理它们,file_type必须为以下几种之一: c, c-header, cpp-out,assemblerassembler-with-cpp, none。所有在命令行中跟在-x后面的文件列表将以指定的类型进行处理。更改类型时,可再次使用-x,例如:
$ gcc -o bigprg mainpart.c -x assemblertrickypart.asm -x c otherpart.c
可以在命令行中多次使用-x来不同的文件类型,选项-x none将关闭类型指示。
18.3.3.2 混合输入类型
可以在GCC命令行中混合多种输入类型,编译器将忽略你请求不进行处理的文件,例如:
$ gcc -c circle.c circulararea.s/usr/lib/libm.a
在上面的命令行中,假设所有指定的文件均存在,GCC编译和装配circle.c 装配circulararea.s, 并忽略库文件,因为-c选项告诉它不做连接,输出结果为两个文件:circle.ocirculararea.o
此帖出自单片机论坛
 
 
 

回复

6366

帖子

4914

TA的资源

版主

8
 
C核心技术手册(四十)
18.3.3.3 动态链接和共享object文件
  共享库为特殊的object文件,它们可以在运行时被连接到程序中来,使用共享链接库具有许多优点:执行文件较小;共享模块允许更新;高效。
创建一个共享object文件,可使用GCC-shared选项目,输入必须是一个存在的object文件。下面是一个简单的例子:
$ gcc -c circulararea.c
$ gcc -shared -o libcirculararea.socirculararea.o
上面第二条命令创建了共享object文件libcirculararea.so,将一个可执行程序与共享object文件连接,在命令和中像使用其他object文件或库文件一样对它进行命名。
$ gcc -c circle.c
$ gcc -o circle circle.o libcirculararea.so–lm
此命令创建了一个可执行程序,它可以在运行时动态地与libcirculararea.so连接,当然,你必须确保程序在运行时能够找到共享库,这可以通过将你的库文件安装在标准的目录下,如/usr/lib,或设置一个适当的环境变量,如LD_LIBRARY_PATH,配置动态库加载的机制因系统不同而有差异。
如果共享库在你的系统上可用,但你应该避免其对潜在的恶意代码开放,例如,可以使用-static选项调用GCC:
$ gcc -static -o circle circle.o circulararea.o–lm
但这样,生成的结果程序将比较大。
C核心技术手册(四十一)
18.3.4 独立程序
除去在GCC命令行中指定的object文件和库文件外,连接器必须连接一些程序启动时所需要的与操作系统强相关的代码,这些代码存在在标准obect文件ctr0.o中,其包含执行程序的入口点(crt代表”C runtime.”),在大多数系统上,GCC也使用默认的object文件crtbegin.octrend.o来连接初始代码。
  然而,如果你在写一个独立程序,例如操作系统或一个嵌入式程序,可以使用-ffreestanding-nostartfiles选项来指示GCC不连接这些代码,选项-nostdlib允许你不使用C标准库,如果你使用此选项,你必须提供程序所使用的其他版本的标准库,最后,在一个独立式环境中,一个C程序不必以main()开始,可以在GCC命令行中使用连接器选项-ename来指定一个可选择的程序入口点。
C核心技术手册(四十二)
18.4 C方言
  当编写一个C程序时,首先要做的一件事是确定在多种C的定义中遵循哪一种,GCC的默认方言版本为“GNU C”,它在很大程度上为ISO/IEC 98991900标准,以及它发布的勘误表和许多语言扩展,这些扩展一部分已经包含在C99中标准化了,例如复杂浮点类型和long long整形,另外一部分特性没有被采用,例如,复杂整形类型和长度为0的数组,全部的扩展列表在GCC文档中有提供。
  要关闭所有GNU C的扩展,可以使用命令行选项 ansi, 本书以ISO/IEC9899:1999 (或C99)来进行描述。
GCC语言标准化选项有:
-std=iso9899:1990, -std=c89, -ansi
  这三个选项表示同一个意思,遵循ISO/IEC 9899:1900,包括技术勘误表19941996, 这不是不接受扩展,而是如果GNU的扩展与ISO标准冲突时,才会禁止,例如typdeof操作符。
-std=iso9899:199409
  遵循“AMD1, 1995图际化对ISO/IEC 9899:1900进行了改善。
-std=iso9899:1999, -std=c99
  遵循ISO/IEC 9899:1999,以用勘误表2001,注意对C99的所有支持依然没有全部完成,可以通过http://gcc.gnu.org/c99status.html来查看当前的发展状态。
-std=gnu89
  支持ISO/IEC 9899:1990,以及GNU扩展,这是GCC默认支持的。
-std=gnu99
  支持ISO/IEC9899:1999,以及GNU扩展,此方言被认为在C99版本的支持完成后,会在新的GCC版本中成为默认的方言。
  对于其中的任何一个选项,如果你想让GCC打印标准版本所要求的所有告警,并拒绝所有被禁止的扩展,你必须添加选项-pedantic。选项-pedantic-errors将会使编译失败,当警告产生时。
  早期的GCC版本提供一个-traditional选项,它用做提供对pre-ANSIK&R风格的C的支持,现在的GCC仅在预编译阶段支持此选项,且当与-E选项一起使用时才接受它,-E的意思是执行预编译,完成后退出。
  此外,许多GCC选项允许你使用或者禁用不同标准及扩展的一些个别方面,例如,-trigraphs选项允许使用三字母词,尽管没有使用-ansi选项,要了解所有方言选项的信息,请查看GCC手册。
C核心技术手册(四十三)
18.5 编译警告
  当编译一个C程序时,有两类麻烦,一是错误信息,这使用你不能完成编译;另一种是警告,它提示你应该遵循严格的标准,便不会中止编译,你也可以不用理会驼些告警,直接运行程序,但这不是一个好主意。
  GCC提供非常好的警告控制,例如,你不喜欢区分错误和警告,可以使用-Werror选项使用GCC在出现任务警告时停止编译,就跟出现错误一样。其他的选项支持古老的或非标准的告警控制。
  使用选项-W可以启用GCC个别的告警,例如,选项-Wswitch-default提示GCC当使用了switch语句,但没有带default标签时出现一个警告。
  使用GCC中的这些警告最简单的方法是在命令行中使用-Wall选项,然而,这个选项的名子容易使用人误解,-Wall并不启用所有独立的-W选项,许多警告要求使用时指定名称,例如-Wshadow,此选项在使用块域定义一个变量,当具有相同的名子时产生一个告警,这些告警-Wall选项是不能产生的。
  使用-Wall选项,且想禁止掉部分警告,可以在-W选项所带的告警名称前面插入no-,因此-Wno-switch-default将关闭告警,这样在使用switch语句且没有带default标签时将不会道理生警告。此外,-w (小写字母)选项将关闭所有告警。
  选项-WexTRa增加许多合法但可疑的表达式相关告警,例如,测试一个无符号值是负数还非负数:
unsigned int u;
/* ... */
if ( u < 0 )
  {/* ... this block is never executed ... */ }
选项-Wextra会产生告警提示表达式没有副作用且结果将被丢弃,所有它可以检查的条件全集在GCC手册中有描述。
  此外,如果你更新古老的程序,可能会使用-WTRaditional选项来产生告警,因为这些在旧风格的CISO 标准C中具有不同的含义,例如,一个在宏中使用的字符串,且包含一个参数:
#define printerror(x)   fputs("x/n", stderr)
在传统的旧的C中,此宏正常工作,但在ISO 标准C中,它将打印出一个字母x和一个换行符,因此-WTRaditional选项将对此行产生下面的警告:
file:line:column: warning: macro argument "x" would bestringified in traditional C
C核心技术手册(四十四)
18.6 优化
  GCC有很多技术可以使用执行文件生成更快更小,这些技巧趋向于减少程序与机器码间的逐字通信速度,它可能使用调试变得很困难,通常用于程序已经被测试和调试过之后。
  有两种优化选项,你可以通过-f开始的选项来单独地使用它们,例如-fmerge-constants,它将使用编译器将相同的常量放置在一个通用的地址,甚至交叉于不同的源文件,也可以使用-o选项(-o0, -o1,-o2-o3)来设置优化级别,它可以一次性启用许多优化技巧。
18.6.1 选项-O的级别
  每一个-o选项代表许多独立的优化技巧,-o优化级别是累加的,-o2包含了-o1中的所有优化项,-o3包含-o2, 想了解不同级别的细节描述和一些-f选项的意义,可以参见GCC参考手册,下面的列表提供了每个级别简单的描述:
-O0
  关闭所有优化选项。
-O, -O1
  试图使用可执行文件更小更快,但却增加了编译的时长,此方法具有合并同一常量,基本循环优化,函数调用后对栈操作进行分组。不带数字的-o选项与-o1的作用是一样的。
-O2
  使用几乎所有的优化方法,但不在程序大小和执行速度间进行权衡。此选项会增加编译时长,除包含-o1的优化项外,编译器执行子表达式清楚任务,即CSE, 此步骤检测程序等价的数据表达式,且重新它们一次,将结果保存在一个没命名的变量中用来重用,此外,指令将被重新安排以减少等待数据从内存向CPU寄存器中移动的时间,还有,数据流分析也在此阶段执行,它允许编译器道理生附加的告警信息来提示没有初始化的变量。
-O3
  生成内联函数,且灵活地向寄存器中安排变量,包含了-O2的优化项。
-Os
  关于大小的选项。此选项与-o2类似,但不会增加代码大小,此外,块重排,函数对齐,跳转被禁止,如果你想得到小的执行文件,你应该使用GCC-s选项,它将使连接器在所有必要的函数和对象连接之后将符号表从执行文件中剥离,这将使用最终程序很小,经常用于构建产品最终版本。
  下面的例子描述了如何使用-o选项:
  $gcc -Wall -O3 -o circle circle.c circulararea.c –lm
C核心技术手册(四十五)
18.6.2 选项-f
  GCC提供许多-f选项供你进行编译优化,例如,你可以使用-o选项来设置一个普通优化项,并关闭某一个技巧,例如:
  $gcc -Wall -O3 -fno-inline-functions -o circle circle.c circulararea.c –lm
选项-o3 fno-inline-functions启用-o3组中的内联函数外的所有优化项。
  当然还有标志可以启用许多优化项,这些并不包含在任务-o级别中,例如 funroll-loops,此选项替代loop语句,所有-f选项有上百个,在本节中无法一一描述,但本节的例子中提供了一个思路,如果你需要某几个编译器特性,从手册中找它是一个很好的选择。
C核心技术手册(四十六)
18.6.3 浮点优化
  一些没有包含在-o级别中的优化选项属于浮点操作,C99标准支持科学记数和高精度的浮点型,但是对于一个应用程序,你可能更关心运行速度而非浮点型变量本身,因此,-ffast-math选项定义了预处理宏__FAST_MATH__  ,指示编译器不用依赖于IEEEISO的浮点数学标准,--ffast-math是一组选项,具有下面六个独立选项:
-fno-math-errno
禁止在数学函数中使用全局变量error
-funsafe-math-optimizations
  “不安全的数学优化”是指那些可能违反浮点数学标准,或者没有确认的参数和结果。
-fno-trapping-math
  产生“不间断”的代码,假设用户不能处理抛出的异常。
-ffinite-math-only
  产生不包含无穷大参数和结果的执行代码。
-fno-rounding-math
  此选项表示程序不依赖于某一个取整的行为,且并不尝试改变浮点环境中的默认取整模式,此设置是当前的默认配置,相反的设置项-frounding-math目前还在实验中。
-fno-signaling-nans
  此选项允许浮点数边界值的优化。
此帖出自单片机论坛
 
 
 

回复

2781

帖子

417

TA的资源

五彩晶圆(中级)

9
 
实在是太给力了~
此帖出自单片机论坛
 
个人签名
 
 

回复
您需要登录后才可以回帖 登录 | 注册

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
关闭
站长推荐上一条 1/9 下一条

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2025 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表