使用getopt()进行命令行处理

 

#include <unistd.h>
extern char *optarg;
// 初始化值为1, 下一次调用getopt时, 从optind存储的位置重新开始检查选项
extern int optind;
// 初始化值为1, 当opterr=0时, getopt不向stderr输出错误信息
extern int opterr;
// 当命令行选项字符不包括在optstring中或者选项缺少必要的参数时
// 该选项存储在optopt中,getopt返回'?'
extern int optopt;
                                   
int getopt(int argc, char * const argv[], const char *optstring);

 

getaddrinfo函数

 

今天在看完APUE第16章:网络IPC:套接字后,就开始编写书中的示例代码,结果出现了问题:

调用getaddrinfo函数时,返回错误:Servname not supported for ai_socktype,觉得很费解,就直接google之,发现下面这篇好文,不仅解决了问题,还使我更加理解了如何使用getaddrinfo来编写服务器/客户端程序

 

原文地址:blog.csdn.net/andyxie407/archive/2007/06/30/1672325.aspx

有这样一个C/S程序,server提供一个叫做ruptime的服务,功能是当有客户端连接时调用uptime程序,并将结果发送到client。可是现在的问题是,这个服务系统本来是没有的,所以调用getaddrinfo的时候会返回如下错误:

代码:
Servname not supported for ai_socktype

我觉得可能是需要编辑/etc/service文件把自己这个服务加进去.

 

个人认为,这个问题就是对getaddrinfo函数的应用和理解,下面帖子的内容基本上是对Advanced Programming in linux Environment这本书里的16-6等几个程序的解释,刚开始对getaddrinfo这个函数和编辑/etc/service等不了解,所会有以上的问题存在。下面是资料。

bumpy:~/tmp$ gcc a.c
bumpy:~/tmp$ ./a.out <==== 没有改/etc/services 前
getaddrinfo error: Servname not supported for ai_socktype
bumpy:~/tmp$ vi a.c
bumpy:~/tmp$ sudo vi /etc/services <==== 添加ruptimed 4000/tcp到合适的位置(你的服务里没有别的是4000吧)
bumpy:~/tmp$ ./a.out
OK

google code 与 svn

这几天试用了google code的项目托管功能,体验了subversion这个版本管理工具。

开始,还以为project hosting就是个简单的文件上传之类的服务呢...汗

"Project Hosting on Google Code provides a free collaborative development environment for open source projects. Each project comes with its own

member controls, Subversion repository, issue tracker, wiki pages, and downloads section. Our project hosting service is simple, fast, reliable, and

scalable, so that you can focus on your own open source development."

                                                                                                                                                                          -----Google 'support' project

主要是想记录下Subversion(svn)的使用方法,由于是第一次接触(其实,在Visual Studio里使用过类似的,不过是图形界面的),还是花了点时间来学习基本使用命令的,主要是参考svnbook:

需要理解的两个重要概念是:

  • 版本库
  • 工作拷贝

常用命令(客户端):

svn help (subcommand): 帮助

svn import: 导入数据到版本库

svn checkout: 签出,创建本地工作拷贝

svn update: 更新工作拷贝

svn commit: 提交

修改:

svn add

svn delete

svn copy

svn move

检验修改:

svn status

svn diff

svn revert: 取消修改

svn resolved: 解决冲突

 

gnu indent 美化C程序代码

参考: http://www.worldhello.net/doc/program_rules/indent.html

google搜索就能找到gnu indent.

The `indent' program can be used to make code easier to read. It can also convert from one style of writing C to another.

 

参照GNU,Kernighan & Ritchie,Berkeley风格,制定了自己风格:

  1. indent命令参数:

    -bad -bap -bbb -bbo -nbc -bl -bli0 -bls -c33 -cd33 -ncdb -ncdw -nce -cli0 -cp33 -cs -d0 -nbfda -di2 -nfc1 -nfca -hnl -ip5 -l75 -lp -pcs -nprs -psl -saf -sai -saw -nsc -nsob -nss -i4 -ts4 -ut

  2. indent配置文件

    如上参数可写入用户目录下的文件:".indent.pro",作为运行indent的确省参数

  3. indent配置说明

    Indent代码格式化说明
    使用的indent参数 含义
    --blank-lines-after-declarations bad 变量声明后加空行
    --blank-lines-after-procedures bap 函数结束后加空行
    --blank-lines-before-block-comments bbb 块注释前加空行
    --break-before-boolean-operator bbo 较长的行,在逻辑运算符前分行
    --blank-lines-after-commas nbc 变量声明中,逗号分隔的变量不分行
    --braces-after-if-line bl "if"和"{"分做两行
    --brace-indent 0 bli0 "{"不继续缩进
    --braces-after-struct-decl-line bls 定义结构,"struct"和"{"分行
    --comment-indentationn c33 语句后注释开始于行33
    --declaration-comment-columnn cd33 变量声明后注释开始于行33
    --comment-delimiters-on-blank-lines ncdb 不将单行注释变为块注释
    --cuddle-do-while ncdw "do --- while"的"while"和其前面的"}"另起一行
    --cuddle-else nce "else"和其前面的"}"另起一行
    --case-indentation 0 cli0 switch中的case语句所进0个空格
    --else-endif-columnn cp33 #else, #endif后面的注释开始于行33
    --space-after-cast cs 在类型转换后面加空格
    --line-comments-indentation n d0 单行注释(不从1列开始的),不向左缩进
    --break-function-decl-args nbfda 关闭:函数的参数一个一行
    --declaration-indentationn di2 变量声明,变量开始于2行,即不必对齐
    --format-first-column-comments nfc1 不格式化起于第一行的注释
    --format-all-comments nfca 不开启全部格式化注释的开关
    --honour-newlines hnl Prefer to break long lines at the position of newlines in the input.
    --indent-leveln i4 设置缩进多少字符,如果为tab的整数倍,用tab来缩进,否则用空格填充。
    --parameter-indentationn ip5 旧风格的函数定义中参数说明缩进5个空格
    --line-length 75 l75 非注释行最长75
    --continue-at-parentheses lp 续行从上一行出现的括号开始
    --space-after-procedure-calls pcs 函数和"("之间插入一个空格
    --space-after-parentheses nprs 在"("后")"前不插入空格
    --procnames-start-lines psl 将函数名和返回类型放在两行定义
    --space-after-for saf for后面有空格
    --space-after-if sai if后面有空格
    --space-after-while saw while后面有空格
    --start-left-side-of-comments nsc 不在生成的块注释中加*
    --swallow-optional-blank-lines nsob 不去掉可添加的空行
    --space-special-semicolon nss 一行的for或while语句,在";"前不加空。
    --tab-size ts4 一个tab为4个空格(要能整除"-in")
    --use-tabs ut 使用tab来缩进

效果:

刚用 indent 美化了根据《C语言接口与实现》中异常一章编写的例子:

预处理之后:

 

int main()
{
 char *buf;

 do { volatile int Except_flag; Except_Frame Except_frame; Except_frame.prev = Except_stack; Except_stack = &Except_frame; Except_flag = _setjmp (Except_frame.env); if(Except_flag == Except_entered) {
  buf = allocate(512);
    if(Except_flag == Except_entered) Except_stack = Except_stack->prev; } else if (Except_frame.exception == &(Allocate_Failed)) { Except_flag = Except_handled;
  fprintf(stderr, "Couldn't allocate the buffer.\n");
 if(Except_flag == Except_entered) Except_stack = Except_stack->prev; } else if (Except_frame.exception == &(Allocate_Success)) { Except_flag = Except_handled;
  fprintf(stderr, "Can allocate the buffer.\n");
 if(Except_flag == Except_entered) Except_stack = Except_stack->prev; } { if(Except_flag == Except_entered) Except_flag = Except_finalized;
  fprintf(stderr, "FINALLY SCOPE.\n");
 if(Except_flag == Except_entered) Except_stack = Except_stack->prev; } if(Except_flag == Except_raised) Except_raise(Except_frame.exception, Except_frame.file, Except_frame.line); } while(0);
}

经 indent 处理后:

 

int
main ()
{
        char *buf;

        do
        {
                volatile int Except_flag;
                Except_Frame Except_frame;

                Except_frame.prev = Except_stack;
                Except_stack = &Except_frame;
                Except_flag = _setjmp (Except_frame.env);
                if (Except_flag == Except_entered)
                {
                        buf = allocate (512);
                        if (Except_flag == Except_entered)
                                Except_stack = Except_stack->prev;
                }
                else if (Except_frame.exception == &(Allocate_Failed))
                {
                        Except_flag = Except_handled;
                        fprintf (stderr, "Couldn't allocate the buffer.\n");
                        if (Except_flag == Except_entered)
                                Except_stack = Except_stack->prev;
                }
                else if (Except_frame.exception == &(Allocate_Success))
                {
                        Except_flag = Except_handled;
                        fprintf (stderr, "Can allocate the buffer.\n");
                        if (Except_flag == Except_entered)
                                Except_stack = Except_stack->prev;
                }
                {
                        if (Except_flag == Except_entered)
                                Except_flag = Except_finalized;
                        fprintf (stderr, "FINALLY SCOPE.\n");
                        if (Except_flag == Except_entered)
                                Except_stack = Except_stack->prev;
                }
                if (Except_flag == Except_raised)
                        Except_raise (Except_frame.exception, Except_frame.file,
                                                  Except_frame.line);
        }
        while (0);
}

 

哇...好好的工具(*^__^*) 嘻嘻……

 

 

gnu binutils 工具解析

内容转自:

www.cublog.cn/u/13991/showart.php

 

GNU binutils是一组二进制工具集。包括:addr2line   ar   gprof   nm   objcopy   objdump   ranlib   size   strings   strip. 本文归纳他们的常用法。

ar


ar用于建立、修改、提取档案文件(archive)。archive是一个包含多个被包含文件的单一文件(也称之为库文件),其结构保证了可以从中检索 并得到原始的被包含文件(称之为archive中的member)。member的原始文件内容、模式(权限)、时间戳、所有着和组等属性都被保存在 archive中。member被提取后,他们的属性被恢复到初始状态。

ar主要用于创建C库文件(关于.o目标文件的生成和共享库的详细介绍,参考gcc笔记)


创建静态库 

    (1) 生成目标文件:  

 

$ gcc -Wall -c file1.c file2.c file3.c

   不用指定生成.o文件名(默认生成file1.o, file2.o, file3.o)。

 

    (2) 从.o目标文件创建静态连接库:

  

$ ar rv libNAME.a file1.o file2.o file3.o

   ar生成了libNAME.a库,并列出库中的文件。

 

   

r :

将flie1.o, file2,o, file3.o插入archive,如故原先archive中已经存在某文件,则先将该文件删除

v :

显示ar操作的附加信息(如被处理的member文件名)

 

注: 对于BSD系统, 还需要在创建静态库之后创建索引: $

ranlib libNAME.a

Linux中不需要这一步(运行它也是无害的).

买了APUE中文版

毕业论文的理论阶段基本结束,这几天等着信息与网络中心提供服务器日志。

人不能闲着阿,是吧?否则不是浪费生命嘛,所以又开始了Unix-C呵呵。

把《Pointers On C》基本看完了,内容很基础,适合初学,加上有C++的基础所以就很快的过了一遍。

 

前天在卓越下单买了《Unix环境高级编程》和《C陷阱与缺陷》,今天中午书就送到了,速度还是很满意的(*^__^*) 嘻嘻……

接下来就准备赶紧把毕设搞定,然后继续我的Unix-C之旅,另外别忘了pku做题。

 

看到好书总有一定要买来的冲动,这不把三本关于C的书都买齐了,艾 ..... 都是money啊

 

另外,打算把大学期间买的一些书在BBS上卖掉咯,但愿还有人要      

 

 

学习计划 Unix-C

鉴于目前毕业论文的压力(还没有开始呢我的毕设),原本的看书计划只能暂时搁置,等这学期忙完再继续。

C:

1. 《C程序设计语言》

     看完电子版,不愧为经典中的经典,有机会一定要去图书馆找到这本书再看一遍。

2. 《C和指针》

     已买,还没有看。

3. 《C专家编程》

     已买,还没有看。

4. 《C陷阱与缺陷》

     没买,不过至少要看电子版的,如果好的话再买

 

Unix & Linux

1. 《Linux程序设计》

      不打算买,已经从图书馆借来看完前六章(前四章的书本例子也已经实际code过),不过为了毕设,还是准备先还了吧,下次从第七章继续

      第五章讲终端,第六章讲curses函数库,都是关于终端下文本在屏幕上的输出控制的,看完,书本例子就算了吧

2. 《Linux高级程序设计》

     不打算买,哪天如果有机会在图书馆碰到的话,就借来看看

3. 《Unix环境高级编程》

     一定要买,不知道是买英文版还是中文版呵呵,从图书馆借来的英文版刚看了两章,感觉写的很好懂

 

Algorithm

算法方面的书已经买了好几本,缺少的就是投入。

 

今记录在此,等忙完毕业论文,再继续

 

编译器&链接器

头文件
用C语言及其他语言进行程序设计时,我们要用头文件来提供对常量的定义和对系统及库函数调用的声明。
对C语言来说,这些头文件几乎总是在/usr/include目录及其子目录下。
调用C语言编译器时,我们可以使用-I标志来包含保存在子目录或非标准位置的include文件, 例如:
      gcc -I/usr/openwin/include fred.c
它指示编译器不仅在标准位置,也在/usr/openwin/include目录中查找程序fred.c中包含的头文件。

另外,用grep命令来搜索包含某些特定定义和函数原型的头文件是很方便的,例如:

查找包含EXIT_的定义的头文件

grep EXIT_ *.h

 

库文件
库是一组预先编译好的函数的集合,这些函数都是按照可重用的原则编写的,他们通常由一组相互关联的函数组成并执行某项常见的任务。
标准系统库文件一般存储在/lib和/usr/lib目录中。C语言编译器(更确切地说,是链接程序)需要知道要搜索哪些库文件,默认情况下,它只搜索标准C语言库。
库文件的名字总是以lib开头,随后的部分指名这是哪种库(例如,c代表C语言库,m代表数学库)。文件名的最后部分以.开始,然后给出库文件的类型:
  .a   代表传统的静态函数库
  .so  代表共享函数库
我们可以通过给出完整的路径名或用-l标志来指示编译器要搜索的库文件,例如:
  gcc -o fred fred.c /usr/lib/libm.a
这条命令指示编译器编译文件fred.c,将编译产生的程序文件命名为fred,并且除搜索标准的C语言函数库外,还搜索数学库以解决函数引用问题,下面的命令也能产生类似的结果:
  gcc -o fred fred.c -lm
-lm是简写形式,它代表的是标准库目录(/usr/lib)中名为libm.a的函数库。-lm标志的另一个好处是如果有共享库,编译器会自动选择共享库。
另外,我们也可以通过-L标志为编译器增加库的搜索路径。例如:
  gcc -o x11fred -L/usr/openwin/lib x11fred.c -lX11
这条命令用/usr/openwin/lib目录中的libX11库版本来编译和链接程序x11fred.c

当程序需要使用函数库中的某个函数时,它包含一个声明该函数的头文件,编译器和链接器负责将程序代码和函数库结合在一起以组成一个单独的可执行文件。

静态库
也称作归档文件(archive)。
我们可以容易的创建和维护自己的静态库,只要使用ar程序和gcc -c命令对函数分别进行编译,例如:
(1)分别编写两个函数的源文件:
//fred.c
#include<stdio.h>
void fred(int arg)
{
   printf("fred: you passed %d\n", arg);
}

//bill.c
#include<stdio.h>
void bill(char *arg)
{
   printf("bill: you passed %s\n", arg);
}

(2)编译为目标文件
  gcc -c bill.c fred.c
 
生成目标文件: bill.o  fred.c
-c选项的作用是阻止编译器创建一个完整的程序,如果此时试图创建一个完整的程序将不会成功,因为尚未定义main函数。

(3)为库文件创建头文件
//lib.h
void fred(int);
void bill(char *arg);

(4)创建库文件
  ar crv libfoo.a bill.o fred.o
 
(5)主程序
//program.c
#include "lib.h"

int main()
{
   bill("Hello World.");
   return 0;
}

(6)编译链接主程序
  gcc -o program program.c -L. -lfoo
  ./program
  输出: bill: you passed Hello World.
-L.选项指示编译器在当前目录中查找函数库。

 

共享库

共享库
静态库一个缺点是,当我们同时运行多个应用程序并且它们都使用来自同一个函数库的函数时,就会在内存中有同一函数的多份拷贝,在程序文件自身中也有多份同样的拷贝,这将消耗大量宝贵的内存和磁盘空间。
共享库克服了上述不足。
共享库的保存位置与静态库是一样的,在典型的Linux系统中,标准数学库的共享版本是/usr/lib/libm.so。
程序使用共享库时,它的链接方式是这样的:它本身不再包含函数代码,而是引用运行时可访问的共享代码。当编译好的程序被装载到内存中执行时,函数引用被解析并产生对共享库的调用,如果
有必要共享库才被加载到内存中。
对Linux系统来说,负责装载共享库并解析客户程序函数引用的程序(动态装载器)是ld.so, 也可能是ld-linux.so.2或ld-lsb.so.1.
我们可以通过运行工具ldd来查看程序需要的共享库:
  ldd program

 

C标准库函数

来源: http://www.duangw.net/computer/languages/c/clib.html

 

本文包括大部分C标准库函数,但没有列出一些用途有限的函数以及某些可以简单的从其他函数合成的函数,也没有包含多字节和本地化函数。

标准库中的各个函数、类型以及宏分别在以下标准头文件中说明:

<assert.h> <float.h> <math.h> <stdarg.h> <stdlib.h>
<ctype.h> <limits.h> <setjmp.h> <stddef.h> <string.h>
<errno.h> <locale.h> <signal.h> <stdio.h> <time.h>

 

1 输入与输出<stdio.h>

头文件<stdio.h>定义了用于输入和输出的函数、类型和宏。最重要的类型是用于声明文件指针的FILE。另外两个常用的类型是 size_t和fpos_t,size_t是由运算符sizeof产生的无符号整类型;fpos_t类型定义能够唯一说明文件中的每个位置的对象。由头部 定义的最有用的宏是EOF,其值代表文件的结尾。

1.1 文件操作

1.1.1 fopen

#include <stdio.h>
FILE *fopen(const char *filename, const char *mode);
返回:成功为FILE指针,失败为NULL

打开以filename所指内容为名字的文件,返回与之关联的流。

mode决定打开的方式,可选值如下:

"r" 打开文本文件用于读
"w" 创建文本文件用于写,并删除已存在的内容(如果有的话)
"a" 添加;打开或创建文本文件用于在文件末尾写
"rb" 打开二进制文件用于读
"wb" 创建二进制文件用于写,并删除已存在的内容(如果有的话)
"ab" 添加;打开或创建二进制文件用于在文件末尾写
"r+" 打开文本文件用于更新(即读和写)
"w+" 创建文本文件用于更新,并删除已存在的内容(如果有的话)
"a+" 添加;打开或创建文本文件用于更新和在文件末尾写
"rb+"或"r+b" 打开二进制文件用于更新(即读和写)
"wb+"或"w+b" 创建二进制文件用于更新,并删除已存在的内容(如果有的话)
"ab+"或"a+b" 添加;打开或创建二进制文件用于更新和在文件末尾写

后六种方式允许对同一文件进行读和写,要注意的是,在写操作和读操作的交替过程中,必须调用fflush()或文件定位函数如fseek()、fsetpos()、rewind()等。

文件名filename的长度最大为FILENAME_MAX个字符,一次最多可打开FOPEN_MAX个文件(在<stdio.h>中定义)。

 

1.1.2 freopen

#include <stdio.h>
FILE *freopen(const char *filename, const char *mode,
FILE *stream);
返回:成功为stream,失败为NULL

以mode指定的方式打开文件filename,并使该文件与流stream相关联。freopen()先尝试关闭与stream关联的文件,不管成功与否,都继续打开新文件。

该函数的主要用途是把系统定义的标准流stdin、stdout、stderr重定向到其他文件。

 

1.1.3 fflush

#include <stdio.h>
int fflush(FILE *stream);
返回:成功为0,失败返回EOF

对输出流(写打开),fflush()用于将已写到缓冲区但尚未写出的全部数据都写到文件中;对输入流,其结果未定义。如果写过程中发生错误则返回EOF,正常则返回0。

fflush(NULL)用于刷新所有的输出流。

程序正常结束或缓冲区满时,缓冲区自动清仓。

 

1.1.4 fclose

#include <stdio.h>
int flcose(FILE *stream);
返回:成功为0,失败返回EOF

刷新stream的全部未写出数据,丢弃任何未读的缓冲区内的输入数据并释放自动分配的缓冲区,最后关闭流。

 

1.1.5 remove

#include <stdio.h>
int remove(const char *filename);
返回:成功为0,失败为非0值

删除文件filename。

 

1.1.6 rename

#include <stdio.h>
int rename(const char *oldfname, const char *newfname);
返回:成功为0,失败为非0值

把文件的名字从oldfname改为newfname。

 

1.1.7 tmpfile

#include <stdio.h>
FILE *tmpfile(void);
返回:成功为流指针,失败为NULL

以方式"wb+"创建一个临时文件,并返回该流的指针,该文件在被关闭或程序正常结束时被自动删除。

 

1.1.8 tmpnam

#include <stdio.h>
char *tmpnam(char s[L_tmpnam]);
返回:成功为非空指针,失败为NULL

若参数s为NULL(即调用tmpnam(NULL)),函数创建一个不同于现存文件名字的字符串,并返回一个指向一内部静态数组的指针。

若s非空,则函数将所创建的字符串存储在数组s中,并将它作为函数值返回。s中至少要有L_tmpnam个字符的空间。

tmpnam函数在每次被调用时均生成不同的名字。在程序的执行过程中,最多只能确保生成TMP_MAX个不同的名字。注意tmpnam函数只是用于创建一个名字,而不是创建一个文件。

 

1.1.9 setvbuf

#include <stdio.h>
int setvbuf(FILE *stream, char *buf, int mode, size_t size);
返回:成功返回0,失败返回非0

控制流stream的缓冲区,这要在读、写以及其他任何操作之前设置。

如果buf非空,则将buf指向的区域作为流的缓冲区,如果buf为NULL,函数将自行分配一个缓冲区。

size决定缓冲区的大小。

mode指定缓冲的处理方式,有如下值:

  • _IOFBF,进行完全缓冲;
  • _IOLBF,对文本文件表示行缓冲;
  • _IOLNF,不设置缓冲。

 

1.1.10 setbuf

#include <stdio.h>
void setbuf(FILE *stream, char *buf);

如果buf为NULL,则关闭流stream的的缓冲区;否则setbuf函数等价于:

    (void)setvbuf(stream, buf, _IOFBF, BUFSIZ)。

注意自定义缓冲区的尺寸必须为BUFSIZ个字节。

 

1.2 格式化输出

1.2.1 fprintf

#include <stdio.h>
int fprintf(FILE *stream, const char *format,…);
返回:成功为实际写出的字符数,出错返回负值

按照format说明的格式把变元表中变元内容进行转换,并写入stream指向的流。

格式化字符串由两种类型的对象组成:普通字符(它们被拷贝到输出流)与转换规格说明(它们决定变元的转换和输出格式)。每个转换规格说明均以字符%开头,以转换字符结束。如果%后面的字符不是转换字符,那么该行为是未定义的。

转换字符列表如下:

字 符 说明
d, i int;有符号十进制表示法
o unsigned int;无符号八进制表示法(无前导0)
x, X unsigned int;无符号十六进制表示法(无前导0X和0x),对0x用abcdef,对0X用ABCDEF
u unsigned int;无符号十进制表示法
c int;单个字符,转换为unsigned char类型后输出
s char *;输出字符串直到'\0'或者达到精度指定的字符数
f double;形如[-]mmm.ddd的十进制浮点数表示法,d的数目由精度确定。缺省精度为6位,精度为0时不输出小数点
e, E double;形如[-]m.dddddde[+-]xx或者[-]m.ddddddE[+-]xx的十进制浮点数表示法,d的数目由精度确定。缺省精度为6位,精度为0时不输出小数点
g G double;当指数值小于-4或大于等于精度时,采用%e或%E的格式;否则使用%f的格式。尾部的0与小数点不打印
p void *;输出指针值(具体表示与实现相关)
n int *;到目前为止以此格式调用函数输出的字符的数目将被写入到相应变元中,不进行变元转换
% 不进行变元转换,输出%

在%与转换字符之间依次可以有下列标记:

标记 说明
- 指定被转换的变元在其字段内左对齐
+ 指定在输出的数前面加上正负号
空格 如果第一个字符不是正负号,那么在其前面附加一个空格
0 对于数值转换,在输出长度小于字段宽度时,加前导0
# 指定其他输出格式,对于o格式,第一个数字必须为零;对于x/X格式,指定在输出的非0值前加0x或0X;对于e/E/f/g/G格式,指定输出总有一个小数点;对于g/GG格式,还指定输出值后面无意义的0将被保留。
宽度[number] 一个指定最小字段宽的数。转换后的变元输出宽度至少要达到这个数值。如果变元的字符数小于此数值,那么在变元左/右边添加填充字符。填充字符通常为空格(设置了0标记则为0)。
. 点号用于把字段宽和精度分开
精度[number] 对于字符串,说明输出字符的最大数目;对于e/E/f格式,说明输出的小数位数;对于g/G格式,说明输出的有效位数;对于整数,说明输出的最小位数(必要时可加前导0)
h/l/L 长度修饰符,h表示对应的变元按short或unsigned short类型输出;l表示对应的变元按long或unsigned long类型输出;L表示对应的变元按long double类型输出

在格式串中字段宽度和精度二者都可以用*来指定,此时该值可通过转换对应的变元来获得,这些变元必须是int类型。

 

1.2.2 printf

#include <stdio.h>
int printf(const char *format, …);

printf(...)等价于fprintf(stdout, ...)。

 

1.2.3 sprintf

#include <stdio.h>
int sprintf(char *buf, const char *format, …);
返回:实际写到字符数组的字符数,不包括'\0'

与printf()基本相同,但输出写到字符数组buf而不是stdout中,并以'\0'结束。

注意,sprintf()不对buf进行边界检查,buf必须足够大,以便能装下输出结果。

 

1.2.4 snprintf

#include <stdio.h>
int snprintf(char *buf, size_t num, const char *format, …);

除了最多为num-1个字符被存放到buf指向的数组之外,snprintf()和sprintf()完全相同。数组以'\0'结束。

该函数不属于C89(C99增加的),但应用广泛,所以将其包括了进来。

 

1.2.5 vprintf

1.2.6 vfprintf

1.2.7 vsprintf

1.2.8 vsnprintf

#include <stdarg.h>
#include <stdio.h>
int vprintf(char *format, va_list arg);
int vfprintf(FILE *stream, const char *format, va_list arg);
int vsprintf(char *buf, const char *format, va_list arg);
int vsnprintf(char *buf, size_t num, const char *format,
va_list arg);

这几个函数与对应的printf()等价,但变元表由arg代替。参见第7节有关<stdarg.h>头文件的讨论。

vsnprintf是C99增加的。

 

1.3 格式化输入

1.3.1 fscanf

#include <stdio.h>
int fscanf(FILE *stream, const char *format, …);
返回:成功为实际被转换并赋值的输入项数目,
到达文件尾或变元被转换前出错为EOF

在格式串format的控制下从流stream中读入字符,把转换后的值赋给后续各个变元,在此每一个变元都必须是一个指针。当格式串format结束时函数返回。

格式串format通常包含有用于指导输入转换的转换规格说明。格式串中可以包含:

  • 空格或制表符,他们将被忽略;
  • 普通字符(%除外),与输入流中下一个非空白字符相匹配;
  • 转换规格说明:由一个%、一个赋值屏蔽字符*(可选)、一个用于指定最大字段宽度的数(可选)、一个用于指定目标字段的字符h/l/L(可选)、一个转换字符组成。

转换规格说明决定了输入字段的转换方式。通常把结果保存在由对应变元指向的变量中。然而,如果转换规格说明中包含有赋值屏蔽字符*,例如%*s,那 么就跳过对应的输入字段,不进行赋值。输入字段是一个由非空白符组成的字符串,当遇到空白符或到达最大字段宽(如果有的话)时,对输入字段的读入结束。这 意味着scanf函数可以跨越行的界限来读入其输入,因为换行符也是空白符(空白符包括空格、横向制表符、纵向制表符、换行符、回车符和换页符)。

转换字符列表如下:

字符 输入数据;变元类型
d 十进制整数;int *
i 整数;int *。该整数可以是以0开头的八进制数,也可以是以0x/0X开头的十六进制数
o 八进制数(可以带或不带前导0);unsigned int *
u 无符号十进制整数;unsigned int *
x 十六进制整数(可以带或不带前导0x/0X);unsigned int *
c 字符;char *。按照字段宽的大小把读入的字符保存在指定的数组中,不加入字符'\0'。字段宽的缺省值为1。在这种情况下,不跳过空白符;如果要读入下一个非空白符,使用%1s(数字1)
s 有非空白符组成的字符串(不包含引号);char *。该变元指针指向一个字符数组,该字符数组有足够空间来保存该字符串以及在末尾添加的'\0'
e/f/g 浮点数;float *。float浮点数的输入格式为:一个任选的正负号,一串可能包含小数点的数字和一个任选的指数字段。指数字段由字母e/E以及后跟的一个可能带正负号的整数组成
p 用printf("%p")调用输出的指针值;void *
n 将到目前为止此调用所读的字符数写入变元;int *。不读入输入字符。不增加转换项目计数
[...] 用方括号括起来的字符集中的字符来匹配输入,以找到最长的非空字符串;char *。在末尾添加'\0'。格式[]...]表示字符集中包含字符]
[^...] 用不在方括号里的字符集中的字符来匹配输入,以找到最长的非空字符串;char *。在末尾添加'\0'。格式[]...]表示字符集中包含字符]
% 字面值%,不进行赋值

字段类型字符:

  • 如果变元是指向short类型而不是int类型的指针,那么在d/i/n/o/u/x这几个转换字符前可以加上字符h
  • 如果变元是指向long类型的指针,那么在d/i/n/o/u/x这几个转换字符前可以加上字符l
  • 如果变元是指向double类型而不是float类型的指针,那么在e/f/g这几个转换字符前可以加上字符l
  • 如果变元是指向long double类型的指针,那么在e/f/g前可以加上字符L

 

1.3.2 scanf

#include <stdio.h>
int scanf(const char *format, …);

scanf(...)等价于fscanf(stdin, ...)。

 

1.3.3 sscanf

#include <stdio.h>
int sscanf(const char *buf, const char *format, …);

与scanf()基本相同,但sscanf()从buf指向的数组中读,而不是stdin。

 

1.4 字符输入输出函数

1.4.1 fgetc

#include <stdio.h>
int fgetc(FILE *stream);

以unsigned char类型返回输入流stream中下一个字符(转换为int类型)。如果到达文件末尾或发生错误,则返回EOF。

 

1.4.2 fgets

#include <stdio.h>
char *fgets(char *str, int num, FILE *stream);
返回:成功返回str,到达文件尾或发生错误返回NULL

从流stream中读入最多num-1个字符到数组str中。当遇到换行符时,把换行符保留在str中,读入不再进行。数组str以'\0'结尾。

 

1.4.3 fputc

#include <stdio.h>
int fputc(int ch, FILE *stream);
返回:成功为所写的字符,出错为EOF

把字符ch(转换为unsigned char类型)输出到流stream中。

 

1.4.4 fputs

#include <stdio.h>
int fputs(const char *str, FILE *stream);
返回:成功返回非负值,失败返回EOF

把字符串str输出到流stream中,不输出终止符'\0'。

 

1.4.5 getc

#include <stdio.h>
int getc(FILE *stream);

getc()与fgetc()等价。不同之处为:当getc函数被定义为宏时,它可能多次计算stream的值。

 

1.4.6 getchar

#include <stdio.h>
int getchar(void);

等价于getc(stdin)。

 

1.4.7 gets

#include <stdio.h>
char *gets(char *str);
返回:成功为str,到达文件尾或发生错误则为NULL

从stdin中读入下一个字符串到数组str中,并把读入的换行符替换为字符'\0'。

gets()可读入无限多字节,所以要保证str有足够的空间,防止溢出。

 

1.4.8 putc

#include <stdio.h>
int putc(int ch, FILE *stream);

putc()与fputc()等价。不同之处为:当putc函数被定义为宏时,它可能多次计算stream的值。

 

1.4.9 putchar

#include <stdio.h>
int putchar(int ch);

等价于putc(ch, stdout)。

 

1.4.10 puts

#include <stdio.h>
int puts(const char *str);
返回:成功返回非负值,出错返回EOF

把字符串str和一个换行符输出到stdout。

 

1.4.11 ungetc

#include <stdio.h>
int ungetc(int ch, FILE *stream);
返回:成功时为ch,出错为EOF

把字符ch(转换为unsigned char类型)写回到流stream中,下次对该流进行读操作时,将返回该字符。对每个流只保证能写回一个字符(有些实现支持回退多个字符),且此字符不能是EOF。

 

1.5 直接输入输出函数

1.5.1 fread

#include <stdio.h>
size_t fread(void *buf, size_t size, size_t count,
FILE *stream);
返回:实际读入的对象数

从流stream中读入最多count个长度为size个字节的对象,放到buf指向的数组中。

返回值可能小于指定读入数,原因可能是出错,也可能是到达文件尾。实际执行状态可用feof()或ferror()确定。

 

1.5.2 fwrite

#include <stdio.h>
size_t fwrite(const void *buf, size_t size, size_t count,
FILE *stream);
返回:实际输出的对象数

把buf指向的数组中count个长度为size的对象输出到流stream中,并返回被输出的对象数。如果发生错误,则返回一个小于count的值。

 

1.6 文件定位函数

1.6.1 fseek

#include <stdio.h>
int fseek(FILE *stream, long int offset, int origin);
返回:成功为0,出错为非0

对流stream相关的文件定位,随后的读写操作将从新位置开始。

对于二进制文件,此位置被定位在由origin开始的offset个字符处。origin的值可能为SEEK_SET(文件开始处)、SEEK_CUR(当前位置)或SEEK_END(文件结束处)。

对于文本流,offset心须为0,或者是由函数ftell()返回的值(此时origin的值必须是SEEK_SET)。

 

1.6.2 ftell

#include <stdio.h>
long int ftell(FILE *stream);

返回与流stream相关的文件的当前位置。出错时返回-1L。

 

1.6.3 rewind

#include <stdio.h>
void rewind(FILE *stream);

rewind(fp)等价于fssek(fp, 0L, SEEK_SET)与clearerr(fp)这两个函数顺序执行的效果,即把与流stream相关的文件的当前位置移到开始处,同时清除与该流相关的文件尾标志和错误标志。

 

1.6.4 fgetpos

#include <stdio.h>
int fgetpos(FILE *stream, fpos_t *position);
返回:成功返回0,失败返回非0

把流stream的当前位置记录在*position中,供随后的fsetpos()调用时使用。

 

1.6.5 fsetpos

#include <stdio.h>
int fsetpos(FILE *stream, const fpos_t *position);
返回:成功返回0,失败返回非0

把流stream的位置定位到*position中记录的位置。*position的值是之前调用fgetpos()记录下来的。

 

1.7 错误处理函数

当发生错误或到达文件末尾时,标准库中的许多函数将设置状态指示符。这些状态指示符可被显式地设置和测试。另外,(定义在<errno.h>中的)整数表达式errno可包含一个出错序号,该数将进一步给出最近一次出错的信息。

1.7.1 clearerr

#include <stdio.h>
void clearerr(FILE *stream);

清除与流stream相关的文件结束指示符和错误指示符。

 

1.7.2 feof

#include <stdio.h>
int feof(FILE *stream);
返回:到达文件尾时返回非0值,否则返回0

与流stream相关的文件结束指示符被设置时,函数返回一个非0值。

 

1.7.3 ferror

#include <stdio.h>
int ferror(FILE *stream);
返回:无错返回0,有错返回非0

与流stream相关的文件出错指示符被设置时,函数返回一个非0值。

 

1.7.4 perror

#include <stdio.h>
void perror(const char *str);

perror(s)用于输出字符串s以及与全局变量errno中的整数值相对应的出错信息,具体出错信息的内容依赖于实现。该函数的功能类似于:

    fprintf(stderr, "%s: %s\n", s, "出错信息");

可参见第3节介绍的strerror函数。

 

 

2 字符类测试<ctype.h>

头文件<ctype.h>中说明了一些用于测试字符的函数。每个函数的变元均为int类型,变元的值必须是EOF或可用 unsigned char类型表示的字符,函数的返回值为int类型。如果变元满足所指定的条件,那么函数返回非0值(表示真);否则返回值为0(表示假)。这些函数包括 2.1~2.11。

在7位ASCII字符集中,可打印字符是从0x20(' ')到0x7E('~')之间的字符;控制字符是从0(NUL)到0x1F(US)之间的字符和字符0x7F(DEL)。

2.1 isalnum

#include <ctype.h>
int sialnum(int ch);

变元为字母或数字时,函数返回非0值,否则返回0。

 

2.2 isalpha

#include <ctype.h>
int isalpha(int ch);

当变元为字母表中的字母时,函数返回非0值,否则返回0。各种语言的字母表互不相同,对于英语来说,字母表由大写和小写的字母A到Z组成。

 

2.3 iscntrl

#include <ctype.h>
int iscntrl(int ch);

当变元是控制字符时,函数返回非0,否则返回0。

 

2.4 isdigit

#include <ctype.h>
int isdigit(int ch);

当变元是十进制数字时,函数返回非0值,否则返回0。

 

2.5 isgraph

#include <ctype.h>
int isgraph(int ch);

如果变元为除空格之外的任何可打印字符,则函数返回非0值,否则返回0。

 

2.6 islower

#include <ctype.h>
int islower(int ch);

如果变元是小写字母,函数返回非0值,否则返回0。

 

2.7 isprint

#include <ctype.h>
int isprint(int ch);

如果变元是可打印字符(含空格),则函数返回非0值,否则返回0。

 

2.8 ispunct

#include <ctype.h>
int ispunct(int ch);

如果变元是除空格、字母和数字外的可打印字符,则函数返回非0,否则返回0。

 

2.9 isspace

#include <ctype.h>
int isspace(int ch);

当变元为空白字符(包括空格、换页符、换行符、回车符、水平制表符和垂直制表符)时,函数返回非0,否则返回0。

 

2.10 isupper

#include <ctype.h>
int isupper(int ch);

如果变元为大写字母,函数返回非0,否则返回0。

 

2.11 isxdigit

#include <ctype.h>
int isxdigit(int ch);

当变元为十六进制数字时,函数返回非0,否则返回0。

 

2.12 tolower

#include <string.h>
int tolower(int ch);

当ch为大写字母时,返回其对应的小写字母;否则返回ch。

 

2.13 toupper

#include <string.h>
int toupper(int ch);

当ch为小写字母时,返回其对应的大写字母;否则返回ch。

 

 

3 字符串函数<string.h>

在头文件<string.h>中定义了两组字符串函数。第一组函数的名字以str开头;第二组函数的名字以mem开头。只有函数memmove对重叠对象间的拷贝进行了定义,而其他函数都未定义。比较类函数将其变元视为unsigned char类型的数组。

3.1 strcpy

#include <string.h>
char *strcpy(char *str1, const char *str2);

把字符串str2(包括'\0')拷贝到字符串str1当中,并返回str1。

 

3.2 strncpy

#include <string.h>
char *strncpy(char *str1, const char *str2, size_t count);

把字符串str2中最多count个字符拷贝到字符串str1中,并返回str1。如果str2中少于count个字符,那么就用'\0'来填充,直到满足count个字符为止。

 

3.3 strcat

#include <string.h>
char *strcat(char *str1, const char *str2);

把str2(包括'\0')拷贝到str1的尾部(连接),并返回str1。其中终止原str1的'\0'被str2的第一个字符覆盖。

 

3.4 strncat

#include <string.h>
char *strncat(char *str1, const char *str2, size_t count);

把str2中最多count个字符连接到str1的尾部,并以'\0'终止str1,返回str1。其中终止原str1的'\0'被str2的第一个字符覆盖。

注意,最大拷贝字符数是count+1。

 

3.5 strcmp

#include <string.h>
int strcmp(const char *str1, const char *str2);

按字典顺序比较两个字符串,返回整数值的意义如下:

  • 小于0,str1小于str2;
  • 等于0,str1等于str2;
  • 大于0,str1大于str2;

 

3.6 strncmp

#include <string.h>
int strncmp(const char *str1, const char *str2, size_t count);

同strcmp,除了最多比较count个字符。根据比较结果返回的整数值如下:

  • 小于0,str1小于str2;
  • 等于0,str1等于str2;
  • 大于0,str1大于str2;

 

3.7 strchr

#include <string.h>
char *strchr(const char *str, int ch);

返回指向字符串str中字符ch第一次出现的位置的指针,如果str中不包含ch,则返回NULL。

 

3.8 strrchr

#include <string.h>
char *strrchr(const char *str, int ch);

返回指向字符串str中字符ch最后一次出现的位置的指针,如果str中不包含ch,则返回NULL。

 

3.9 strspn

#include <string.h>
size_t strspn(const char *str1, const char *str2);

返回字符串str1中由字符串str2中字符构成的第一个子串的长度。

 

3.10 strcspn

#include <string.h>
size_t strcspn(const char *str1, const char *str2);

返回字符串str1中由不在字符串str2中字符构成的第一个子串的长度。

 

3.11 strpbrk

#include <string.h>
char *strpbrk(const char *str1, const char *str2);

返回指向字符串str2中的任意字符第一次出现在字符串str1中的位置的指针;如果str1中没有与str2相同的字符,那么返回NULL。

 

3.12 strstr

#include <string.h>
char *strstr(const char *str1, const char *str2);

返回指向字符串str2第一次出现在字符串str1中的位置的指针;如果str1中不包含str2,则返回NULL。

 

3.13 strlen

#include <string.h>
size_t strlen(const char *str);

返回字符串str的长度,'\0'不算在内。

 

3.14 strerror

#include <string.h>
char *strerror(int errnum);

返回指向与错误序号errnum对应的错误信息字符串的指针(错误信息的具体内容依赖于实现)。

 

3.15 strtok

#include <string.h>
char *strtok(char *str1, const char *str2);

在str1中搜索由str2中的分界符界定的单词。

对strtok()的一系列调用将把字符串str1分成许多单词,这些单词以str2中的字符为分界符。第一次调用时str1非空,它搜索 str1,找出由非str2中的字符组成的第一个单词,将str1中的下一个字符替换为'\0',并返回指向单词的指针。随后的每次strtok()调用 (参数str1用NULL代替),均从前一次结束的位置之后开始,返回下一个由非str2中的字符组成的单词。当str1中没有这样的单词时返回 NULL。每次调用时字符串str2可以不同。

如:

char *p;
p = strtok("The summer soldier,the sunshine patriot", " ");
printf("%s", p);
do {
    p = strtok("\0", ", "); /* 此处str2是逗号和空格 */
    if (p)
        printf("|%s", p);
} while (p);

显示结果是:The | summer | soldier | the | sunshine | patriot

 

3.16 memcpy

#include <string.h>
void *memcpy(void *to, const void *from, size_t count);

把from中的count个字符拷贝到to中。并返回to。

 

3.17 memmove

#include <string.h>
void *memmove(void *to, const void *from, size_t count);

功能与memcpy类似,不同之处在于,当发生对象重叠时,函数仍能正确执行。

 

3.18 memcmp

#include <string.h>
int memcmp(const void *buf1, const void *buf2, size_t count);

比较buf1和buf2的前count个字符,返回值与strcmp的返回值相同。

 

3.19 memchr

#include <string.h>
void *memchr(const void *buffer, int ch, size_t count);

返回指向ch在buffer中第一次出现的位置指针,如果在buffer的前count个字符当中找不到匹配,则返回NULL。

 

3.20 memset

#include <string.h>
void *memset(void *buf, int ch, size_t count);

把buf中的前count个字符替换为ch,并返回buf。

 

 

4 数学函数<math.h>

头文件<math.h>中说明了数学函数和宏。

宏EDOM和ERANGE(定义在头文件<errno.h>中)是两个非0整常量,用于引发各个数学函数的定义域错误和值域错 误;HUGE_VAL是一个double类型的正数。当变元取值在函数的定义域之外时,就会出现定义域错误。在发生定义域错误时,全局变量errno的值 被置为EDOM,函数的返回值视具体实现而定。如果函数的结果不能用double类型表示,那么就会发生值域错误。当结果上溢时,函数返回 HUGE_VAL并带有正确的符号(正负号),errno的值被置为ERANGE。当结果下溢时,函数返回0,而errno是否被设置为ERANGE视具 体实现而定。

4.1 sin

#include <math.h>
double sin(double arg);

返回arg的正弦值,arg单位为弧度。

 

4.2 cos

#include <math.h>
double cos(double arg);

返回arg的余弦值,arg单位为弧度。

 

4.3 tan

#include <math.h>
double tan(double arg);

返回arg的正切值,arg单位为弧度。

 

4.4 asin

#include <math.h>
double asin(double arg);

返回arg的反正弦值sin-1(x),值域为[-pi/2,pi/2], 其中变元范围[-1,1]。

 

4.5 acos

#include <math.h>
double acos(double arg);

返回arg的反余弦值cos-1(x),值域为[0,pi], 其中变元范围[-1,1]。

 

4.6 atan

#include <math.h>
double atan(double arg);

返回arg的反正切值tan-1(x),值域为[-pi/2,pi/2]。

 

4.7 atan2

#include <math.h>
double atan2(double a, double b);

返回a/b的反正切值tan-1(a/b),值域为[-pi,pi]。

 

4.8 sinh

#include <math.h>
double sinh(double arg);

返回arg的双曲正弦值。

 

4.9 cosh

#include <math.h>
double cosh(double arg);

返回arg的双曲余弦值。

 

4.10 tanh

#include <math.h>
double tanh(double arg);

返回arg的双曲正切值。

 

4.11 exp

#include <math.h>
double exp(double arg);

返回幂函数ex

 

4.12 log

#include <math.h>
double log(double arg);

返回自然对数ln(x),其中变元范围arg > 0。

 

4.13 log10

#include <math.h>
double log10(double arg);

返回以10为底的对数log10(x),其中变元范围arg > 0。

 

4.14 pow

#include <math.h>
double pow(double x, double y);

返回xy,如果x=0且y<=0或者如果x<0且y不是整数,那么产生定义域错误。

 

4.15 sqrt

#include <math.h>
double sqrt(double arg);

返回arg的平方根,其中变元范围arg>=0。

 

4.16 ceil

#include <math.h>
double ceil(double arg);

返回不小于arg的最小整数。

 

4.17 floor

#include <math.h>
double floor(double arg);

返回不大于arg的最大整数。

 

4.18 fabs

#include <math.h>
double fabs(double arg);

返回arg的绝对值|x|。

 

4.19 ldexp

#include <math.h>
double ldexp(double num, int exp);

返回num * 2exp

 

4.20 frexp

#include <math.h>
double frexp(double num, int *exp);

把num分成一个在[1/2,1)区间的真分数和一个2的幂数。将真分数返回,幂数保存在*exp中。如果num等于0,那么这两部分均为0。

 

4.21 modf

#include <math.h>
double modf(double num, double *i);

把num分成整数和小数两部分,两部分均与num有同样的正负号。函数返回小数部分,整数部分保存在*i中。

 

4.22 fmod

#include <math.h>
double fmod(double a, double b);

返回a/b的浮点余数,符号与a相同。如果b为0,那么结果由具体实现而定。

 

 

5 实用函数<stdlib.h>

在头文件<stdlib.h>中说明了用于数值转换、内存分配以及具有其他相似任务的函数。

5.1 atof

#include <stdlib.h>
double atof(const char *str);

把字符串str转换成double类型。等价于:strtod(str, (char**)NULL)。

 

5.2 atoi

#include <stdlib.h>
int atoi(const char *str);

把字符串str转换成int类型。等价于:(int)strtol(str, (char**)NULL, 10)。

 

5.3 atol

#include <stdlib.h>
long atol(const char *str);

把字符串str转换成long类型。等价于:strtol(str, (char**)NULL, 10)。

 

5.4 strtod

#include <stdlib.h>
double strtod(const char *start, char **end);

把字符串start的前缀转换成double类型。在转换中跳过start的前导空白符,然后逐个读入构成数的字符,任何非浮点数成分的字符都会终止上述过程。如果end不为NULL,则把未转换部分的指针保存在*end中。

如果结果上溢,返回带有适当符号的HUGE_VAL,如果结果下溢,那么函数返回0。在这两种情况下,errno均被置为ERANGE。

 

5.5 strtol

#include <stdlib.h>
long int strtol(const char *start, char **end, int radix);

把字符串start的前缀转换成long类型,在转换中跳过start的前导空白符。如果end不为NULL,则把未转换部分的指针保存在*end中。

如果radix的值在2到36间之间,那么转换按该基数进行;如果radix为0,则基数为八进制、十进制、十六进制,以0为前导的是八进制,以 0x或0X为前导的是十六进制。无论在哪种情况下,串中的字母是表示10到radix-1之间数字的字母。如果radix是16,可以加上前导0x或 0X。

如果结果上溢,则依据结果的符号返回LONG_MAX或LONG_MIN,置errno为ERANGE。

 

5.6 strtoul

#include <stdlib.h>
unsigned long int strtoul(const char *start, char **end,
int radix);

与strtol()类似,只是结果为unsigned long类型,溢出时值为ULONG_MAX。

 

5.7 rand

#include <stdlib.h>
int rand(void);

产生一个0到RAND_MAX之间的伪随机整数。RAND_MAX值至少为32767。

 

5.8 srand

#include <stdlib.h>
void srand(unsigned int seed);

设置新的伪随机数序列的种子为seed。种子的初值为1。

 

5.9 calloc

#include <stdlib.h>
void *calloc(size_t num, size_t size);

为num个大小为size的对象组成的数组分配足够的内存,并返回指向所分配区域的第一个字节的指针;如果内存不足以满足要求,则返回NULL。

分配的内存区域中的所有位被初始化为0。

 

5.10 malloc

#include <stdlib.h>
void *malloc(size_t size);

为大小为size的对象分配足够的内存,并返回指向所分配区域的第一个字节的指针;如果内存不足以满足要求,则返回NULL。

不对分配的内存区域进行初始化。

 

5.11 realloc

#include <stdlib.h>
void *realloc(void *ptr, size_t size);

将ptr指向的内存区域的大小改为size个字节。如果新分配的内存比原内存大,那么原内存的内容保持不变,增加的空间不进行初始化。如果新分配的 内存比原内存小,那么新内存保持原内存区中前size字节的内容。函数返回指向新分配空间的指针。如果不能满足要求,则返回NULL,原ptr指向的内存 区域保持不变。

如果ptr为NULL,则行为等价于malloc(size)。

如果size为0,则行为等价于free(ptr)。

 

5.12 free

#include <stdlib.h>
void free(void *ptr);

释放ptr指向的内存空间,若ptr为NULL,则什么也不做。ptr必须指向先前用动态分配函数malloc、realloc或calloc分配的空间。

 

5.13 abort

#include <stdlib.h>
void abort(void);

使程序非正常终止。其功能类似于raise(SIGABRT)。

 

5.14 exit

#include <stdlib.h>
void exit(int status);

使程序正常终止。atexit函数以与注册相反的顺序被调用,所有打开的文件被刷新,所有打开的流被关闭。status的值如何被返回依具体的实现而定,但用0表示正常终止,也可用值EXIT_SUCCESS和EXIT_FAILURE。

 

5.15 atexit

#include <stdlib.h>
int atexit(void (*func)(void));

注册在程序正常终止时所要调用的函数func。如果成功注册,则函数返回0值,否则返回非0值。

 

5.16 system

#include <stdlib.h>
int system(const char *str);

把字符串str传送给执行环境。如果str为NULL,那么在存在命令处理程序时,返回0值。如果str的值非NULL,则返回值与具体的实现有关。

 

5.17 getenv

#include <stdlib.h>
char *getenv(const char *name);

返回与name相关的环境字符串。如果该字符串不存在,则返回NULL。其细节与具体的实现有关。

 

5.18 bsearch

#include <stdlib.h>
void *bsearch(const void *key, const void *base, size_t n,
size_t size,	int (*compare)(const void *, const void *));

在base[0]...base[n-1]之间查找与*key匹配的项。size指出每个元素占有的字节数。函数返回一个指向匹配项的指针,若不存在匹配则返回NULL。

函数指针compare指向的函数把关键字key和数组元素比较,比较函数的形式为:

int func_name(const void *arg1, const void *arg2);

arg1是key指针,arg2是数组元素指针。

返回值必须如下:

  • arg1 < arg2时,返回值<0;
  • arg1 == arg2时,返回值==0;
  • arg1 > arg2时,返回值>0。

数组base必须按升序排列(与compare函数定义的大小次序一致)。

 

5.19 qsort

#include <stdlib.h>
void qsort(void *base, size_t n, size_t size, \
	int (*compare)(const void *, const void *));

对由n个大小为size的对象构成的数组base进行升序排序。

比较函数compare的形式如下:

int func_name(const void *arg1, const voie *arg2);

其返回值必须如下所示:

  • arg1 < arg2,返回值<0;
  • arg1 == arg2,返回值==0;
  • arg1 > arg2,返回值>0。

 

5.20 abs

#include <stdlib.h>
int abs(int num);

返回int变元num的绝对值。

 

5.21 labs

#include <stdlib.h>
long labs(long int num);

返回long类型变元num的绝对值。

 

5.22 div

#include <stdlib.h>
div_t div(int numerator, int denominator);

返回numerator/denominator的商和余数,结果分别保存在结构类型div_t的两个int成员quot和rem中。

 

5.23 ldiv

#include <stdlib.h>
ldiv_t div(long int numerator, long int denominator);

返回numerator/denominator的商和余数,结果分别保存在结构类型ldiv_t的两个long成员quot和rem中。

 

 

6 诊断<assert.h>

6.1 assert

#include <assert.h>
void assert(int exp);

assert宏用于为程序增加诊断功能。当assert(exp)执行时,如果exp为0,则在标准出错输出流stderr输出一条如下所示的信息:

    Assertion failed: expression, file filename, line nnn

然后调用abort终止执行。其中的源文件名filename和行号nnn来自于预处理宏__FILE__和__LINE__。

如果<assert.h>被包含时定义了宏NDEBUG,那么宏assert被忽略。

 

 

7 变长变元表<stdarg.h>

头文件<stdarg.h>中的说明提供了依次处理含有未知数目和类型的函数变元表的机制。

7.1 va_start

7.2 va_arg

7.3 va_end

#include <stdarg.h>
void va_start(va_list ap, lastarg);
type va_arg(va_list ap, type);
void va_end(va_list ap);

假设函数f含有可变数目的变元,lastarg是它的最后一个有名参数,然后在f内说明一个类型为va_list的变量ap,它将依次指向每个变元:

    va_list ap;

在访问任何未命名的变元前必须用va_start宏对ap进行初始化:

    va_start(ap, lastarg);

此后,宏va_arg的每次执行将产生一个与下一个未命名的变元有相同类型和值的值,它同时还修改ap,以使下一次使用va_arg时返回下一个变元:

    va_arg(ap, type);

当所有的变元处理完毕之后,f返回之前,必须调用一次宏va_end:

   va_end(ap);

例子:函数sum_series()的第一个参数是变元项数。

double sum_series(int num, … )
{
    double sum = 0.0, t;
    va_list ap;

    va_start(ap, num);
    for (; num; num--) {
        t = va_arg(ap, double);
        sum += t;
    }
    va_end(ap);
    return sum;
}

 

 

8 非局部跳转<setjmp.h>

头文件<setjmp.h>中的说明提供了一种避免通常的函数调用和返回顺序的途径,特别的,它允许立即从一个多层嵌套的函数调用中返回。

8.1 setjmp

#include <setjmp.h>
int setjmp(jmp_buf env);

setjmp()宏把当前状态信息保存到env中,供以后longjmp()恢复状态信息时使用。如果是直接调用setjmp(),那么返回值为 0;如果是由于调用longjmp()而调用setjmp(),那么返回值非0。setjmp()只能在某些特定情况下调用,如在if语句、switch 语句及循环语句的条件测试部分以及一些简单的关系表达式中。

 

8.2 longjmp

#include <setjmp.h>
void longjmp(jmp_buf env, int val);

longjmp()用于恢复由最近一次调用setjmp()时保存到env的状态信息。当它执行完时,程序就象setjmp()刚刚执行完并返回非 0值val那样继续执行。包含setjmp()宏调用的函数一定不能已经终止。所有可访问的对象的值都与调用longjmp()时相同,唯一的例外是,那 些调用setjmp()宏的函数中的非volatile自动变量如果在调用setjmp()后有了改变,那么就变成未定义的。

 

 

9 信号处理<signal.h>

头文件<signal.h>中提供了一些用于处理程序运行期间所引发的异常条件的功能,如处理来源于外部的中断信号或程序执行期间出现的错误等事件。

9.1 signal

#include <signal.h>
void (*signal(int sig, void (*handler)(int)))(int);

signal()用于确定以后当信号sig出现时的处理方法。如果handler的值是SIG_DFL,那么就采用实现定义的缺省行为;如果 handler的值是SIG_IGN,那么就忽略该信号;否则,调用handler所指向的函数(参数为信号类型)。有效的信号包括:

SIGABRT 异常终止,如调用abort()。
SIGFPE 算术运算出错,如除数为0或溢出。
SIGILL 非法函数映象,如非法指令。
SIGINT 交互式信号,如中断。
SIGSEGV 非法访问存储器,如访问不存在的内存单元。
SIGTERM 发送给本程序的终止请求信号。

signal()返回信号sig原来的的handler;如果出错,则返回SIG_ERR。

当随后出现信号sig时,就中断正在执行的操作,转而执行信号处理函数(*handler)(sig)。如果从信号处理程序中返回,则从中断的位置继续执行。

信号的初始状态由实现定义。

 

9.2 raise

#include <signal.h>
int raise(int sig);

向程序发送信号sig。如果发送不成功,就返回一个非0值。

 

 

10 日期与时间函数<time.h>

头文件<time.h>中说明了一些用于处理日期和时间的类型和函数。其中的一部分函数用于处理当地时间,因为时区等原因,当地时间与 日历时间可能不相同。clock_t和time_t是两个用于表示时间的算术类型,而struct tm则用于存放日历时间的各个成分。tm的各个成员的用途及取值范围如下:

    int tm_sec; /* 秒,0~61 */
    int tm_min; /* 分,0~59 */
    int tm_hour; /* 时,0~23 */
    int tm_mday; /* 日,1~31 */
    int tm_mon; /* 月(从1月开始),0~11 */
    int tm_year; /* 年(从1900年开始) */
    int tm_wday; /* 星期(从周日开始),0~6 */
    int tm_yday; /* 天数(从1月1日开始),0~365 */
    int tm_isdst; /* 夏令时标记 */

其中,tm_isdst在使用夏令时时其值为正,在不使用夏令时时其值为0,如果该信息不能使用,其值为负。

10.1 clock

#include <time.h>
clock_t clock(void);

返回程序自开始执行到目前为止所占用的处理机时间。如果处理机时间不可使用,那么返回-1。clock()/CLOCKS_PER_SEC是以秒为单位表示的时间。

 

10.2 time

#include <time.h>
time_t time(time_t *tp);

返回当前日历时间。如果日历时间不能使用,则返回-1。如果tp不为NULL,那么同时把返回值赋给*tp。

 

10.3 difftime

#include <time.h>
double difftime(time_t time2, time_t time1);

返回time2-time1的值(以秒为单位)。

 

10.4 mktime

#include <time.h>
time_t mktime(struct tm *tp);

将结构*tp中的当地时间转换为time_t类型的日历时间,并返回该时间。如果不能转换,则返回-1。

 

10.5 asctime

#include <time.h>
char *asctime(const struct tm *tp);

将结构*tp中的时间转换成如下所示的字符串形式:

    day month date hours:minutes:seconds year\n\0

如:

    Fri Apr 15 15:14:13 2005\n\0

返回指向该字符串的指针。字符串存储在可被其他调用重写的静态对象中。

 

10.6 ctime

#include <time.h>
char *ctime(const time_t *tp);

将*tp中的日历时间转换为当地时间的字符串,并返回指向该字符串指针。字符串存储在可被其他调用重写的静态对象中。等价于如下调用:

    asctime(localtime(tp))

 

10.7 gmtime

#include <time.h>
struct tm *gmtime(const time_t *tp);

将*tp中的日历时间转换成struct tm结构形式的国际标准时间(UTC),并返回指向该结构的指针。如果转换失败,返回NULL。结构内容存储在可被其他调用重写的静态对象中。

 

10.8 localtime

#include <time.h>
struct tm *localtime(const time_t *tp);

将*tp中的日历时间转换成struct tm结构形式的本地时间,并返回指向该结构的指针。结构内容存储在可被其他调用重写的静态对象中。

 

10.9 strftime

#include <time.h>
size_t strftime(char *s, size_t smax, const char *fmt, \
	const struct tm *tp);

根据fmt的格式说明把结构*tp中的日期与时间信息转换成指定的格式,并存储到s所指向的数组中,写到s中的字符数不能多于smax。函数返回实际写到s中的字符数(不包括'\0');如果产生的字符数多于smax,则返回0。

fmt类似于printf()中的格式说明,它由0个或多个转换规格说明与普通字符组成。普通字符原封不动的拷贝到s中,每个%c按照下面所描述的格式用与当地环境相适应的值来替换。转换规格列表如下:

格式 说明
%a   一星期中各天的缩写名
%A 一星期中各天的全名
%b 缩写月份名
%B 月份全名
%c 当地时间和日期表示
%d 用整数表示的一个月中的第几天(01~31)
%H 用整数表示的时(24小时制,00~23)
%I 用整数表示的时(12小时制,01~12)
%j 用整数表示的一年中各天(001~366)
%m 用整数表示的月份(01~12)
%M 用整数表示的分(00~59)
%p 与AM/PM对应的当地表示方法
%S 用整数表示的秒(00~61)
%U 用整数表示一年中的星期数(00~53,将星期日看作为每周的第一天)
%w 用整数表示一周中的各天(0~6,星期日为0)
%W 用整数表示一年中的星期数(00~53,将星期一看作为每周的第一天)
%x 当地日期表示
%X 当地时间表示
%y 不带公元的年(00~99)
%Y 完整年份表示
%Z 时区名字(可获得时)
%% %本身

 

 

11 由实现定义的限制<limits.h>和<float.h>

头文件<limits.h>中定义了用于表示整类型大小的常量。以下所列的值是可接受的最小值,实际系统中可能有更大的值。

CHAR_BIT 8 char类型的位数
CHAR_MAX UCHAR_MAXSCHAR_MAX   char类型的最大值
CHAR_MIN 0SCHAR_MIN char类型的最小值
INT_MAX 32767 int类型的最大值
INT_MIN -32767 int类型的最小值
LONG_MAX 2147483647 long的最大值
LONG_MIN -2147483647 long类型的最小值
SCHAR_MAX +127 signed char类型的最大值
SCHAR_MIN -127 signed char类型的最小值
SHRT_MAX +32767 short类型的最大值
SHRT_MIN -32767 short类型的最小值
UCHAR_MAX 255 unsigned char类型的最大值
UINT_MAX 65535 unsigned int类型的最大值
ULONG_MAX 4294967295 unsigned long的最大值
USHRT_MAX 65535 unsigned short的最大值

 

以下是<float.h>的一个子集,是与浮点算术运算相关的一些常量。给出的每个值代表相应量的一个最小取值。实际实现可以定义适当的值。

FLT_RADIX 2 指数表示的基数,如2、16
FLT_ROUNDS   加法的浮点舍入规则
FLT_DIG 6 float类型精度(小数位数)
FLT_EPSILON 1E-5 使“1.0 + x != 1.0”成立的最小x
FLT_MANT_DIG   基数为FLT_RADIX的尾数中的数字数
FLT_MAX 1E+37 最大浮点数
FLT_MAX_EXP   使FLT_RADIXn-1可表示的最大n
FLT_MIN 1E-37 最小的规范化浮点数
FLT_MIN_EXP   使10n为规范化数的最小n
DBL_DIG 10 double类型精度(小数位数)
DBL_EPSILON 1E-9 使“1.0 + x != 1.0”成立的最小x
DBL_MANT_DIG   基数为FLT_RADIX的尾数中的数字数
DBL_MAX 1E+37 最大双精度浮点数
DBL_MAX_EXP   使FLT_RADIXn-1可表示的最大n
DBL_MIN 1E-37 最小的规范化双精度浮点数
DBL_MIN_EXP   使10n为规范化数的最小n

 


GCC 详解

文章来源: www.cublog.cn/u/13991/showart.php

 

The History of GCC


1984年,Richard Stallman发起了自由软件运动,GNU (Gnu's Not Unix)项目应运而生,3年后,最初版的GCC横空出世,成为第一款可移植、可优化、支持ANSI C的开源C编译器。
GCC最初的全名是GNU C Compiler,之后,随着GCC支持的语言越来越多,它的名称变成了GNU Compiler Collection。
这里介绍的gcc是GCC的前端,C编译器.


警告信息


    -Wall : 显示所有常用的编译警告信息。
    -W    : 显示更多的常用编译警告,如:变量未使用、一些逻辑错误。

    -Wconversion : 警告隐式类型转换。
    -Wshadow : 警告影子变量(在代码块中再次声明已声明的变量)
    -Wcast-qual :警告指针修改了变量的修饰符。如:指针修改const变量。
    -Wwrite-strings : 警告修改const字符串。
    -Wtraditional : 警告ANSI编译器与传统C编译器有不同的解释。
    -Werror : 即使只有警告信息,也不编译。(gcc默认:若只有警告信息,则进行编译,若有错误信息,则不编译)


C语言标准


 

你可以在gcc的命令行中通过指定选项来选择相应的C语言标准: 从传统c到最新的GNU扩展C. 默认情况下, gcc使用最新的GNU C扩展.

    -ansi : 关闭GNU扩展中与ANSI C相抵触的部分。
    -pedantic          : 关闭所有的GNU扩展。
    -std=c89           : 遵循C89标准
    -std=c99           : 遵循C99标准
    -std=traditional : 使用原始C
注意:后4个选项可以与-ansi结合使用,也可以单独使用。

可在gcc中使用大量GNU C扩展.

生成特定格式的文件


以hello.c为例子,可以设置选项生成hello.i, hello.s, hello.o以及最终的hello文件:

    hello.c : 最初的源代码文件;
    hello.i : 经过编译预处理的源代码;
    hello.s : 汇编处理后的汇编代码;
    hello.o : 编译后的目标文件,即含有最终编译出的机器码,但它里面所引用的其他文件中函数的内存位置尚未定义。
    hello / a.out : 最终的可执行文件

    (还有.a(静态库文件), .so(动态库文件), .s(汇编源文件)留待以后讨论)

如果你不通过-o指定生成可执行文件名,那么会默认生成a.out. 不指定生成文件名肯能覆盖你上次生成的a.out.

e.g.
$ gcc hello.c
在不给gcc传递任何参数的情况下, gcc执行默认的操作: 将源文件编译为目标文件--> 将目标文件连接为可执行文件(名为a.out) --> 删除目标文件.

-c生成.o文件时,默认生成与源代码的主干同名的.o文件。比如对应hello.c生成hello.o. 但也可在生成目标文件时指定目标文件名(注意同时要给出.o后缀): $ gcc -c -o demo.o demo.c

    $ gcc -Wall -c hello.c              : 生成hello.o
    $ gcc -Wall -c -save-temps hello.c  : 生成hello.i, hello.s, hello.o
    注意-Wall 选项的使用场合:仅在涉及到编译(即会生成.o文件时,用-Wall)  

多文件编译、连接


如果原文件分布于多个文件中:file1.c, file2,c
    $ gcc -Wall file1.c file2.c -o name

若对其中一个文件作了修改,则可只重新编译该文件,再连接所有文件:
    $ gcc -Wall -c file2.c
    $ gcc file1.c file2.o -c name

注意:若编译器在命令行中从左向右顺序读取.o文件,则它们的出现顺序有限制:含有某函数定义的文件必须出现在含有调用该函数的文件之后。好在GCC无此限制。

编译预处理


以上述的hello.c为例, 要对它进行编译预备处理, 有两种方法: 在gcc中指定-E选项, 或直接调用cpp.gcc的编译预处理命令程序为cpp,比较新版本的gcc已经将cpp集成了,但仍提供了cpp命令. 可以直接调用cpp命令, 也可以在gcc中指定-E选项指定它只进行编译预处理.

 

$

gcc -E hello.c                            ==  $ cpp hello.c

上述命令马上将预处理结果显示出来. 不利于观看. 可采用-c将预处理结果保存:

 

$

gcc -E -c hello.i hello.c              ==  $ cpp -o hello.i hello.c

注意, -c指定名称要给出".i"后缀.

 

另外, gcc针对编译预处理提供了一些选项:

 

(1) 除了直接在源代码中用 #define NAME来定义宏外,gcc可在命令行中定义宏:-DNAME(其中NAME为宏名),  也可对宏赋值: -DNAME=value 注意等号两边不能有空格! 由于宏扩展只是一个替换过程,也可以将value换成表达式,但要在两边加上双括号: -DNAME="statement"

 

e.g. $

gcc -Wall -DVALUE="2+2" tmp.c -o tmp

如果不显示地赋值,如上例子,只给出:-DVALUE,gcc将使用默认值:1.

 

(2) 除了用户定义的宏外, 有一些宏是编译器自动定义的,它们以__开头,运行: $

cpp -dM /dev/null

, 可以看到这些宏. 注意, 其中含有不以__开头的非ANSI宏,它们可以通过-ansi选项被禁止。

 

 


查看宏扩展

1, 运行 $

gcc -E test.c

,gcc对test.c进行编译预处理,并立马显示结果. (不执行编译) 2, 运行 $

gcc -c -save-temps test.c

,不光产生test.o,还产生test.i, test.s,前者是编译预处理结果, 后者是汇编结果.

 

   


利用Emacs查看编译预处理结果

针对含有编译预处理命令的代码,可以利用emacs方便地查看预处理结果,而不需执行编译,更为方便的是,可以只选取一段代码,而非整个文件:

 

1,选择想要查看的代码

 

2,C-c C-e (M-x c-macro-expand)

 

这样,就自动在一个名为"Macroexpansion"的buffer中显示pre-processed结果.



生成汇编代码


使用"-S"选项指定gcc生成以".s"为后缀的汇编代码:

 

$

gcc -S hello.c

$

gcc -S -o hello.s hello.c

生成汇编语言的格式取决于目标平台. 另外, 如果是多个.c文件, 那么针对每一个.c文件生成一个.s文件.

 

包含头文件


在程序中包含与连接库对应的头文件是很重要的方面,要使用库,就一定要能正确地引用头文件。一般在代码中通过#include引入头文件, 如果头文件位于系统默认的包含路径(/usr/includes), 则只需在#include中给出头文件的名字, 不需指定完整路径.  但若要包含的头文件位于系统默认包含路径之外, 则有其它的工作要做: 可以(在源文件中)同时指定头文件的全路径. 但考虑到可移植性,最好通过-I在调用gcc的编译命令中指定。

下面看这个求立方的小程序(阴影语句表示刚开始不存在):

#include <stdio.h>
#include <math.h>
int main(int argc, char *argv[])
{
  double x = pow (2.0, 3.0);
  printf("The cube of 2.0 is %f\n", x);
  return 0;
}

使用gcc-2.95来编译它(-lm选项在后面的连接选项中有介绍, 这里只讨论头文件的包含问题):
$ gcc-2.95 -Wall pow.c -lm -o pow_2.95
pow.c: In function `main':
pow.c:5: warning: implicit declaration of function `pow'

程序编译成功,但gcc给出警告: pow函数隐式声明。
$ ./pow_2.95
The cube of 2.0 is 1.000000

明显执行结果是错误的,在源程序中引入头文件(#include <math.h>),消除了错误。

不要忽略Warning信息!它可能预示着,程序虽然编译成功,但运行结果可能有错。故,起码加上"-Wall"编译选项!并尽量修正Warning警告。

搜索路径

首先要理解 #include<file.h>和#include"file.h"的区别:
#include<file.h>只在默认的系统包含路径搜索头文件
#include"file.h"首先在当前目录搜索头文件, 若头文件不位于当前目录, 则到系统默认的包含路径搜索头文件.

UNIX类系统默认的系统路径为:

头文件,包含路径: /usr/local/include/  or  /usr/include/
库文件,连接路径: /usr/local/lib/          or  /usr/lib/ 
  

对于标准c库(glibc或其它c库)的头文件, 我们可以直接在源文件中使用#include <file.h>来引入头文件.

如果要在源文件中引入自己的头文件, 就需要考虑下面的问题:

1, 如果使用非系统头文件, 头文件和源文件位于同一个目录, 如何引用头文件呢?
——我们可以简单地在源文件中使用 #include "file.h", gcc将当前目录的file.h引入到源文件. 如果你很固执, 仍想使用#include <file.h>语句, 可以在调用gcc时添加"-I."来将当前目录添加到系统包含路径. 细心的朋友可能会想到: 这样对引用其它头文件会不会有影响? 比如, #include<file.h>之后紧接着一个#include<math.h>, 它能正确引入math.h吗? 答案是: 没有影响. 仍然能正确引用math.h. 我的理解是: "-I."将当前目录作为包含路径的第一选择, 若在当前目录找不到头文件, 则在默认路径搜索头文件. 这实际上和#include"file.h"是一个意思.

2, 对于比较大型的工程, 会有许多用户自定义的头文件, 并且头文件和.c文件会位于不同的目录. 又该如何在.c文件中引用头文件呢?
—— 可以直接在.c文件中利用#include“/path/file.h", 通过指定头文件的路径(可以是绝对路径, 也可以是相对路径)来包含头文件. 但这明显降低了程序的可移植性. 在别的系统环境下编译可能会出现问题. 所以还是利用"-I"选项指定头文件完整的包含路径.

针对头文件比较多的情况, 最好把它们统一放在一个目录中, 比如~/project/include. 这样就不需为不同的头文件指定不同的路径. 如果你嫌每次输入这么多选项太麻烦, 你可以通过设置环境变量来添加路径:
$ C_INCLUDE_PATH=/opt/gdbm-1.8.3/include
$ export C_INCLUDE_PATH
$ LIBRART_PATH=/opt/gdbm-1.8.3/lib
$ export LIBRART_PATH

可一次指定多个搜索路径,":"用于分隔它们,"."表示当前路径,如:
$ C_INCLUDE_PATH=.:/opt/gdbm-1.8.3/include:/net/include
$ LIBRARY_PATH=.:/opt/gdbm-1.8.3/lib:/net/lib
(可以添加多个路径,路径之间用:相隔,.代表当前目录,若.在最前头,也可省略)

当然,若想永久地添加这些路径,可以在.bash_profile中添加上述语句.

3, 还有一个比较猥琐的办法: 系统默认的包含路径不是/usr/include或/usr/local/include么? 我把自己的头文件拷贝到其中的一个目录, 不就可以了么? 的确可以这样, 如果你只想在你自己的机器上编译运行这个程序的话.

前面介绍了三种添加搜索路径的方法,如果这三种方法一起使用,优先级如何呢?

 

命令行设置 > 环境变量设置 > 系统默认

 

与外部库连接


前面介绍了如何包含头文件. 而头文件和库是息息相关的, 使用库时, 要在源代码中包含适当的头文件,这样才能声明库中函数的原型(发布库时, 就需要给出相应的头文件).

 

和包含路径一样, 系统也有默认的连接路径:


头文件,包含路径: /usr/local/include/  or  /usr/include/
库文件,连接路径: /usr/local/lib/          or  /usr/lib/  

同样地, 我们想要使用某个库里的函数, 必须将这个库连接到使用那些函数的程序中.

 

有一个例外: libc.a或libc.so (C标准库,它包含了ANSI C所定义的C函数)是不需要你显式连接的, 所有的C程序在运行时都会自动加载c标准库.

 

除了C标准库之外的库称之为"外部库", 它可能是别人提供给你的, 也可能是你自己创建的(后面有介绍如何创建库的内容).

 

外部库有两种:(1)静态连接库lib.a
                     (2)共享连接库lib.so

两者的共同点:
    .a, .so都是.o目标文件的集合,这些目标文件中含有一些函数的定义(机器码),而这些函数将在连接时会被最终的可执行文件用到。

两者的区别:
    静态库.a  : 当程序与静态库连接时,库中目标文件所含的所有将被程序使用的函数的机器码被copy到最终的可执行文件中. 静态库有个缺点: 占用磁盘和内存空间. 静态库会被添加到和它连接的每个程序中, 而且这些程序运行时, 都会被加载到内存中. 无形中又多消耗了更多的内存空间.

    共享库.so : 与共享库连接的可执行文件只包含它需要的函数的引用表,而不是所有的函数代码,只有在程序执行时, 那些需要的函数代码才被拷贝到内存中, 这样就使可执行文件比较小, 节省磁盘空间(更进一步,操作系统使用虚拟内存,使得一份共享库驻留在内存中被多个程序使用).共享库还有个优点: 若库本身被更新, 不需要重新编译与 它连接的源程序。

静态库

下面我们来看一个简单的例子,计算2.0的平方根(假设文件名为sqrt.c):

#include <math.h>
#include <stdio.h>
int
main (void)
{
double x = sqrt (2.0);
printf ("The square root of 2.0 is %f\n", x);
return 0;
}

用gcc将它编译为可执行文件:
$ gcc -Wall sqrt.c -o sqrt
编译成功,没有任何警告或错误信息。执行结果也正确。
$ ./sqrt
The square root of 2.0 is 1.414214
    
下面我们来看看刚才使用的gcc版本:
$ gcc --version
  gcc (GCC) 4.0.2 20050808 (prerelease) (Ubuntu 4.0.1-4ubuntu9)

现在我用2.95版的gcc把sqrt.c再编译一次:
$ gcc-2.95 -Wall sqrt.c -o sqrt_2.95
  /tmp/ccVBJd2H.o: In function `main':
  sqrt.c:(.text+0x16): undefined reference to `sqrt'
     collect2: ld returned 1 exit status
    
编 译器会给出上述错误信息,这是因为sqrt函数不能与外部数学库"libm.a"相连。sqrt函数没有在程序中定义,也不存在于默认C库 "libc.a"中,如果用gcc-2.95,应该显式地选择连接库。上述出错信息中的"/tmp/ccVBJd2H.o"是gcc创造的临时目标文件, 用作连接时用。

使用下列的命令可以成功编译:
$ gcc-2.95 -Wall sqrt.c /usr/lib/libm.a -o sqrt_2.95
它告知gcc:在编译sqrt.c时,加入位于/usr/lib中的libm.a库(C数学库)。

C库文件默认位于/usr/lib, /usr/local/lib系统目录中; gcc默认地从/usr/local/lib, /usr/lib中搜索库文件。(在我的Ubuntu系统中,C库文件位于/urs/lib中。

这里还要注意连接顺序的问题,比如上述命令,如果我改成:
$ gcc-2.95 -Wall /usr/lib/libm.a sqrt.c -o sqrt_2.95
gcc会给出出错信息:
 /tmp/cc6b3bIa.o: In function `main':
 sqrt.c:(.text+0x16): undefined reference to `sqrt'
 collect2: ld returned 1 exit status

正如读取目标文件的顺序,gcc也在命令行中从左向右读取库文件——任何包含某函数定义的库文件必须位于调用该函数的目标文件之后!

指定库文件的绝对路径比较繁琐,有一种简化方法,相对于上述命令,可以用下面的命令来替代:
$ gcc-2.95 -Wall sqrt.c -lm -o sqrt_2.95
其中的"-l"表示与库文件连接,"m"代表"libm.a"中的m。一般而言,"-lNAME"选项会使gcc将目标文件与名为"libNAME.a"的库文件相连。(这里假设使用默认目录中的库,对于其他目录中的库文件,参考后面的“搜索路径”。)

上面所提到的"libm.a"就是静态库文件,所有静态库文件的扩展名都是.a!
$ whereis libm.a
  libm: /usr/lib/libm.a /usr/lib/libm.so

正如前面所说,默认的库文件位于/usr/lib/或/usr/local/lib/目录中。其中,libm.a是静态库文件,libm.so是后面会介绍的动态共享库文件。

如果调用的函数都包含在libc.a中(C标准库被包含在/usr/lib/libc.a中,它包含了ANSI C所定义的C函数)。那么没有必要显式指定libc.a:所有的C程序运行时都自动包含了C标准库!(试试 $ gcc-2.95 -Wall hello.c -o hello)。

共享库

正因为共享库的优点,如果系统中存在.so库,gcc默认使用共享库(在/usr/lib/目录中,库文件以共享和静态两种版本存在)。 

运行:$ gcc -Wall -L. hello.c -lNAME -o hello
gcc先检查是否有替代的libNAME.so库可用。   

正如前面所说,共享库以.so为扩展名(so == shared object)。

那么,如果不想用共享库,而只用静态库呢?可以加上 -static选项
$ gcc -Wall -static hello.c -lNAME -o hello
它等价于:
$ gcc -Wall hello.c libNAME.a -o hello

$ gcc-2.95 -Wall sqrt.c -static -lm -o sqrt_2.95_static
$ gcc-2.95 -Wall sqrt.c -lm -o sqrt_2.95_default
$ gcc-2.95 -Wall sqrt.c /usr/lib/libm.a -o sqrt_2.95_a
$ gcc-2.95 -Wall sqrt.c /usr/lib/libm.so -o sqrt_2.95_so

$ ls -l sqrt*
-rwxr-xr-x  1 zp zp  21076 2006-04-25 14:52 sqrt_2.95_a
-rwxr-xr-x  1 zp zp   7604 2006-04-25 14:52 sqrt_2.95_default
-rwxr-xr-x  1 zp zp   7604 2006-04-25 14:52 sqrt_2.95_so
-rwxr-xr-x  1 zp zp 487393 2006-04-25 14:52 sqrt_2.95_static

上述用四种方式编译sqrt.c,并比较了可执行文件的大小。奇怪的是,-static -lm 和 /lib/libm.a为什么有区别?有知其原因着,恳请指明,在此谢谢了! :)

如果libNAME.a在当前目录,应执行下面的命令:
$ gcc -Wall -L. hello.c -lNAME -o hello
-L.表示将当前目录加到连接路径。

利用GNU archiver创建库

$ ar cr libhello.a hello_fn.o by_fn.o
从hello_fn.o和by_fn.o创建libihello.a,其中cr表示:creat & replace
$ ar t libhello.a
列出libhello.a中的内容,t == table
(也可创建libhello.so)

关于创建库的详细介绍,可参考本blog的GNU binutils笔记


调试

 


一般地,可执行文件中是不包含任何对源代码的参考的,而debugger要工作,就要知道目标文件/可执行文件中的机器码对应的源代码的信息(如:哪条语 句、函数名、变量名...). debugger工作原理:将函数名、变量名,对它们的引用,将所有这些对象对应的代码行号储存到目标文件或可执行文件的符 号表中。

GCC提供-g选项,将调试信息加入到目标文件或可执行文件中。
$ gcc -Wall -g hello.c -o hello

注意:若发生了段错误,但没有core dump,是由于系统禁止core文件的生成!
$ ulimit -c  ,若显示为0,则系统禁止了core dump

解决方法:
$ ulimit -c unlimited  (只对当前shell进程有效)
或在~/.bashrc 的最后加入: ulimit -c unlimited (一劳永逸)

优化


GCC具有优化代码的功能,代码的优化是一项比较复杂的工作,它可归为:源代码级优化、速度与空间的权衡、执行代码的调度。

GCC提供了下列优化选项:
    -O0  : 默认不优化(若要生成调试信息,最好不优化)
    -O1  : 简单优化,不进行速度与空间的权衡优化;   
    -O2  : 进一步的优化,包括了调度。(若要优化,该选项最适合,它是GNU发布软件的默认优化级别;
    -O3  : 鸡肋,兴许使程序速度更慢;
    -funroll-loops  : 展开循环,会使可执行文件增大,而速度是否增加取决于特定环境;
    -Os  : 生成最小执行文件;

一般来说,调试时不优化,一般的优化选项用-O2(gcc允许-g与-O2联用,这也是GNU软件包发布的默认选项),embedded可以考虑-Os。

注意:此处为O!(非0或小写的o,-o是指定可执行文件名)。

检验优化结果的方法:$ time ./prog

time测量指定程序的执行时间,结果由三部分组成:
    real : 进程总的执行时间, 它和系统负载有关(包括了进程调度,切换的时间)
    user: 被测量进程中用户指令的执行时间
    sys  : 被测量进程中内核代用户指令执行的时间

user和sys的和被称为CPU时间.

注意:对代码的优化可能会引发警告信息,移出警告的办法不是关闭优化,而是调整代码