显示标签为“gcc”的博文。显示所有博文
显示标签为“gcc”的博文。显示所有博文

2007年8月30日星期四

gcc中格式化输出函数的研究

来源:http://www.linuxaid.com.cn/articles/3/1/311618806.shtml

By 书生


1.前言

  在gcc编程中,我们比较经常用到的字符格式化输出函数是printf的,实际上gcc继承了c语言处理字符具有强大功能的风格,它提供了一系列的格式化输出函数,主要存在两个库函数文件stdio.h/ stdarg.h中,具体函数如下:

#include
  printf, int printf(const char *format, ...);
  fprintf, int fprintf(FILE *stream, const char *format, ...);
  sprintf, int sprintf(char *str, const char *format, ...);
  snprintf, int snprintf(char *str, size_t size, const char *format, ...);

  #include
  vprintf, vprintf(const char *format, va_list ap);
  vfprintf, vfprintf(FILE *stream, const char *format, va_list ap);
  vsprintf, int vsprintf(char *str, const char *format, va_list ap);

  其中:

  printf和vprintf函数主要是向一个标准输出设备或标准的字符流输出格式化后的字符。

  Fprintf和vfprintf 函数主要是向一个给定的字符流设备输出格式化后的字符。

  而sprintf, snprintf, vsprintf 和vsnprintf函数则是将格式化后的结果赋值给一个字符串。

  虽然函数的功能上有差异,返回的值的类型也不尽相同,但是在建立匹配格式的语法上还是相同的,这几个函数都有一些共同特征,就是你要设计好模板和格式化的字串。这些函数格式化字符串的命令主要是通过模板字符串中跟在“%”后面的字符来控制的。

  如下一个例子中:

int pct = 37;
  char filename[] = "foo.txt";
  printf ("Processing of `%s' is %d%% finished.Please be patient.",filename, pct);

  显然,这个例子的打印结果就是如下:

Processing of `foo.txt' is 37% finished.Please be patient.

  如上例子我们可以看出一般格式化函数的语法特点就是包含格式化匹配的字符串,输出的字串和变量组合的结构。

2.参数详细介绍

  由于大部分函数在如何格式化字串部分的语法非常相似,我们先研究他们的共同特点,然后再应用不同的例子来分析其不同特点:此类函数一般的调用格式为:printf("<格式化字符串>", <参量表>);

   其中格式化字符串包括两部分内容: 一部分是正常字符, 这些字符将按原样输出; 另一部分是格式化规定字符, 以"%"开始, 后跟一个或几个规定字符,用来确定输出内容格式。参量表是需要输出的一系列参数, 其个数必须与格式化字符串所说明的输出参数个数一样多, 各参数之间用","分开, 且顺序一一对应, 否则将会出现意想不到的错误。

2.1 Gcc提供的格式化规定符如下:

  • %d
  • 十进制有符号整数

  • %i
  • 十进制有符号整数

    注:上面这两个函数在格式化输出时用途是相同的,但在输入时却是不一样的,%i可以接受任何形式的整数,而%d却不能。

  • %u
  • 十进制无符号整数

  • %f
  • 输出浮点数

  • %s
  • 输出字符串

  • %c
  • 输出单个字符

  • %p
  • 输出指针的值

  • %e %E
  • 指数形式的浮点数 ,其中:%e是以小写形式输出的 %E是以大写形式输出的

  • %x, %X
  • 无符号以十六进制表示的整数,其中:%x是以小写形式输出的 %X是以大写形式输出的

  • `%g', `%G'
  • 根据输出数据的大小需要决定用普通形式还是指数形式的输出方式,其中: %g是以小写形式输出的 %G是以大写形式输出的

  • %o
  • 无符号以八进制表示的整数

  • `%c
  • '输出单个字符

  • %n
  • 得到输出字符的个数,但是本参数不产生任何的输出效果。

  • `%m'
  • 输出错误时的相应的字符串提示。

  • `%%'
  • 输出正文字符中的“%”字符

  说明:

  1. 可 以在"%"和字母之间插进数字表示最大场宽。例如: %3d 表示输出3位整型数, 不够3位右对齐。%9.2f 表示输出场宽为9的浮点数, 其中小数位为2, 整数位为6,小数点占一位, 不够9位右对齐。%8s 表示输出8个字符的字符串, 不够8个字符右对齐。如果字符串的长度、或整型数位数超过说明的场宽, 将按其实际长度输出。但对浮点数, 若整数部分位数超过了说明的整数位宽度, 将按实际整数位输出;若小数部分位数超过了说明的小数位宽度, 则按说明的宽度以四舍五入输出。另外, 若想在输出值前加一些0, 就应在场宽项前加个0。例如: %04d 表示在输出一个小于4位的数值时, 将在前面补0使其总宽度为4位。如果用浮点数表示字符或整型量的输出格式, 小数点后的数字代表最大宽度,小数点前的数字代表最小宽度。
  2. 例如: %6.9s 表示显示一个长度不小于6且不大于9的字符串。若大于9, 则第9个字符以后的内容将被删除。

  3. 可以在"%"和字母之间加小写字母l, 表示输出的是长型数。
  4. 例如: %ld 表示输出long整数%lf 表示输出double浮点数

  5. 可以控制输出左对齐或右对齐, 即在"%"和字母之间加入一个"-" 号可
  6. 说明输出为左对齐, 否则为右对齐。例如: %-7d 表示输出7位整数左对齐%-10s表示输出10个字符左对齐

2.2 一些特殊规定字符

  • 换行
  • f 清屏并换页
  • 回车
  • Tab符
  • xhh 表示一个ASCII码用16进表示,其中hh是1到2个16进制数

3.格式化转换的具体细则

3.1 整数转换部分

   整数转换部分主要是'%d', '%i', '%o', '%u', '%x', 和 '%X'这几个参数命令的,由于参数的不同,可以输出不同格式的结果。如上表所列: '%d', '%i'是输出一个带符号的十进制的数,'%o', '%u', and '%x'是输出一个不带符号的数,而'%X是'%x''的大写形式。其中,针对这几种不同输出选择还有如下几个参数项:

  • '-’ 表示是左对齐,一般都是右对齐的。
  • '+’ 是对'%d', '%i'两个参数而言的,是指以'+’符号表示正数
  • ' ' 是对'%d', '%i'两个参数而言的,如果输出不是以'+’'-’开头的,那么用空格做开头。
  • '#' 是对'%o'参数而言的,将在输出的结果强制加上'0’为开头。
  • ''' 将输出的数字以LC_NUMERIC的分类法用’,’隔开。
  • '0' 将空格的地方用'0'填入。

  如果没有特别指明,被格式化的参数被默认当作整数处理,或者可以用以下的类型指定参数来进行修改,如下:

  • 'h' 指定传入参数是 short int 或unsigned short int类型的
  • 'l' 指定传入参数是 long int或unsigned long int类型的
  • 'q' 指定传入参数是 long long int类型的
  • 'Z' 指定传入参数是size_t.。

  为了方便理解给出一个例子:

  1. 对于如下的格式化匹配字串:
  2. "|%5d|%-5d|%+5d|%+-5d|% 5d|%05d|%5.0d|%5.2d|%d|"

    将产生类似如下的输出:

    | 0|0 | +0|+0 | 0|00000| | 00|0|
    | 1|1 | +1|+1 | 1|00001| 1| 01|1|
    | -1|-1 | -1|-1 | -1|-0001| -1| -01|-1|
    |100000|100000|+100000| 100000|100000|100000|100000|100000|
  3. 对于如下的格式化匹配字串:
  4. "|%5u|%5o|%5x|%5X|%#5o|%#5x|%#5X|%#10.8x|"

    将产生类似如下的输出:

    | 0| 0| 0| 0| 0| 0x0| 0X0|0x00000000|
    | 1| 1| 1| 1| 01| 0x1| 0X1|0x00000001|
    |100000|303240|186a0|186A0|0303240|0x186a0|0X186A0|0x000186a0|

3.2 浮点数的转换部分

  浮点数转换部分主要是'%f', '%e', '%E', '%g', 和 '%G' '这几个参数命令的,由于参数的不同,可以输出不同格式的结果。如上表所列: '%f'是输出一个比较固定形式的浮点数……其中,针对这几种不同输出选择还有如下几个参数项:

  • '-’ 表示是左对齐,一般都是右对齐的。
  • '+’ 是指以'+’符号表示正数
  • ' ' 如果输出不是以'+’'-’开头的,那么用空格做开头
  • '#' 是对'%g'和'%G’参数而言的,将在输出的结果强制加上'0’为开头。
  • ''' 将输出的数字以LC_NUMERIC的分类法用’,’隔开。
  • '0' 将空格的地方用“0'填入。

  如果没有特别指定,传入的被格式化的参数默认是double类型的,可以用'L’表示是一个long double类型的。

  如下例子可以看出浮点数格式化的字串:

  如下的格式字串:

"|%12.4f|%12.4e|%12.4g|"

  可能产生如下的输出:

| 0.0000| 0.0000e+00| 0|
  | 1.0000| 1.0000e+00| 1|
  | -1.0000| -1.0000e+00| -1|
  | 100.0000| 1.0000e+02| 100|
  | 1000.0000| 1.0000e+03| 1000|
  | 10000.0000| 1.0000e+04| 1e+04|
  | 12345.0000| 1.2345e+04| 1.234e+04|
  | 100000.0000| 1.0000e+05| 1e+05|
  | 123456.0000| 1.2346e+05| 1.234e+05|

3.3 其他格式的转换部分

  这部分的函数比较简单一些,具体如下:

  • '%c’是指输出一个单个的字符串,默认的输出的被格式化的参数是unsigned char类型的,可以用'-’表示左对齐的。没有的别的参数,比如:
  • printf ("%c%c%c%c%c", 'h', 'e', 'l', 'l', 'o');

    显示结果为: 'hello'

  • '%s’是输出一个字串,. 默认的输出的被格式化的参数是char * (or const char *). 类型的,可以用'-’表示左对齐的。没有的别的参数,比如:
  • printf ("%3s%-6s", "no", "where");

    显示结果: ' nowhere '.

      注: 如果你用这个参数来格式化输出一个指针类型的参数时,有可能会得到一个'(null)'的输出值。不过有时候用于指针为空的缘故程序运行时会产生“Segmentation fault”的错误,下面一个例子就会产生这样的错误:

    #include
    main()
    {
    char a;
    a = inet_addr("192.168.1.1");
    if(a!=-1){
    printf("ip:%s",a);/* 这里的%s可能会产生错误,应改用用%p比较好一些*/
    }
    }

  • '%m’是输出error信息的。如下例子:
  • fprintf (stderr, "can't open '%s': %m", filename);

    等于如下的输出命令:

    fprintf (stderr, "can't open '%s': %s", filename, strerror (errno));

  • “%p”是输出指针类型参数的,显然被格式化的输入蚕室必须是指针,可以用“-”来表示左对齐的。
  • “%n”是比较特殊的参数,它不对格式化输出影响,而是得到输出结果的字符长度,可以用类型指定参数'h' 和 'l'来分别指定输出的参数分别是short int *和 long int *类型的。如下面的例子:

  • int nchar;
    printf ("%d %s%n", 3, "bears", &nchar);

    输出结果:

    3 bears

    同时将7的值赋给变量nchar。

  • '%%'是输出“%”的字符。

4.函数具体介绍

4.1printf()函数

  printf()函数是格式化输出函数系列中比较有具有普遍特点的, 一般用于向标准输出设备按规定格式输出信息。在编写程序时经常会用到此函数。printf()函数的调用格式为:

  printf("<格式化字符串>", <参量表>);

#include
  #include
  int main()
  {
  char c, s[20], *p;
  int a=1234, *i;
  float f=3.141592653589;
  double x=0.12345678987654321;
  p="How do you do";
  strcpy(s, "Hello, Comrade");
  *i=12;
  c='x41';
  printf("a=%d", a); /*结果输出十进制整数a=1234*/
  printf("a=%6d", a); /*结果输出6位十进制数a= 1234*/
  printf("a=%06d", a); /*结果输出6位十进制数a=001234*/
  printf("a=%2d", a); /*a超过2位, 按实际值输出a=1234*/
  printf("*i=%4d", *i); /*输出4位十进制整数*i= 12*/
  printf("*i=%-4d", *i); /*输出左对齐4位十进制整数*i=12*/
  printf("i=%p", i); /*输出地址i=06E4*/
  printf("f=%f", f); /*输出浮点数f=3.141593*/
  printf("f=6.4f", f); /*输出6位其中小数点后4位的浮点数f=3.1416*/
  printf("x=%lf", x); /*输出长浮点数x=0.123457*/
  printf("x=%18.16lf", x);/*输出18位其中小数点后16位的长浮点数x=0.1234567898765432*/
  printf("c=%c", c); /*输出字符c=A*/
  printf("c=%x", c); /*输出字符的ASCII码值c=41*/
  printf("s[]=%s", s); /*输出数组字符串s[]=Hello, Comrade*/
  printf("s[]=%6.9s", s);/*输出最多9个字符的字符串s[]=Hello,Co*/
  printf("s=%p", s); /*输出数组字符串首字符地址s=FFBE*/
  printf("*p=%s", p); /* 输出指针字符串p=How do you do*/
  printf("p=%p", p); /*输出指针的值p=0194*/
  getch();
  retunr 0;
  }

  上面结果中的地址值在不同计算机上可能不同。

  例子中第一条语句#include的 含义是调用另一个文件stdio.h, 这是一个头文件, 其中包括全部标准输入输出库函数的数据类型定义和函数说明。对每个库函数便用的变量及函数类型都已作了定义与说明, 放在相应头文件"*.h"中, 用户用到这些函数时必须要用#include<*.h>或#include"*.h" 语句调用相应的头文件, 以供若没有用此语句说明, 则连接时将会出现错误。

4.2 fprintf()函数

  fprintf( ) 函数中格式化的规定与printf( ) 函数相同, 所不同的只是fprintf()函数是向文件中写入。而printf()是向屏幕输出。

  下面介绍一个例子, 运行后产后一个test.dat的文件。

#include
  main()
  {
  char *s="That's good news"}; /*定义字符串指针并初始化*/
  int i=617; /*定义整型变量并初始化*/
  FILE *fp; /*定义文件指针*/
  fp=fopne("test.dat", "w"); /*建立一个文字文件只写*/
  fputs("Your score of TOEFLis", fp);/*向所建文件写入一串字符*/
  fputc(':', fp); /*向所建文件写冒号:*/
  fprintf(fp, "%d", i); /*向所建文件写一整型数*/
  fprintf(fp, "%s", s); /*向所建文件写一字符串*/
  fclose(fp); /*关闭文件*/
  }

  用CAT命令显示TEST.DAT的内容如下所示:屏幕显示

Your score of TOEFL is: 617
  That's good news

4.3 sprintf() 函数

  sprintf(string, fmt, ...)传回的是string的类型的数组,并以空字符结尾。不过,该函数有可能超过为字符分配的长度。比较危险。下面是一个sprintf()的事例。

int
  //根据传进来的Mission数据结构,建立socket链接,取得文件的大小。
  get_size_of_url(struct Mission* pms)
  {
  int s;
  struct sockaddr_in sin;
  struct hostent* phe;
  char cmd[256];
  char msg_hdr[1000];
  char* p;
  //准备http中GET 方法的请求。
  sprintf(cmd,"GET %s HTTP/1.0", pms->url);
  //创建socket
  if((s=socket(PF_INET,SOCK_STREAM,0))<0)
  return -1;
  //取得远程主机的IP地址,失败函数返回-1
  if((phe = gethostbyname(pms->host)) == NULL)
  return -1;
  memset(&sin,0,sizeof(sin));
  memcpy(&sin.sin_addr,phe->h_addr,sizeof(struct in_addr));
  sin.sin_family=AF_INET;
  sin.sin_port=htons(pms->port);
  //跟远程机器建立连接,失败函数返回-1
  if(connect(s,(struct sockaddr*)&sin,sizeof(sin))==-1)
  return -1;
  //发送GET请求
  if(write(s,cmd,strlen(cmd))<0)
  return 0;
  //从链接描述符(连接管道)中读取传送过来的数据
  if(read(s, msg_hdr, 300)<0)
  return 0;
  close(s);
  printf("%s",msg_hdr);
  //读到该文件的大小
  if((p=strstr(msg_hdr,"Content-Length"))||(p=strstr(msg_hdr,"Content-length:")))
  p+=16;
  else
  return 0;
  //返回大小
  return atoi(p);
  }

  注:在大部份的Unix系统上,sprintf(string, fmt, ...)传回的是string的指标,然而,这方面Linux(遵循ANSI)传回的却是放入string内的字元数目.进行移植时,尤其是针对SunOS,需有警觉的心。

4.4 Snprintf()函数

  Snprintf()函数与Sprintf()函数极为相似,但是该函数多了size参数来表示最大的字符数目,该函数返回一个整数值表示被存储的字符的数目,如果返回-1则表示输出的字符空间不够。如下例子:

char *
  make_message (char *name, char *value)
  {
  /* 预分配100个字符空间. */
  int size = 100;
  char *buffer = (char *) xmalloc (size);
  while (1)
  {
  /* 输出格式化的字符到给定的空间中. */
  int nchars = snprintf (buffer, size,"value of %s is %s",name, value);
  /* 判断是否返回真值 */
  if (nchars < size)
  return buffer;
  /* 如果空间不够,加大预分配空间到2倍 */
  size *= 2;
  buffer = (char *) xrealloc (size, buffer);
  }
  }

4.5 asprintf()函数

  int asprintf (char **ptr, const char *template, ...)

  本函数跟sprintf()函数很类似,只是它将字符串的分配改成动态分配的形式,参数ptr是指一个char *对象的地址函数返回指向一个新建的指针。如下例子:

/* Construct a message describing the value of a variable whose name is name and whose value is value. */
  char *
  make_message (char *name, char *value)
  {
  char *result;
  asprintf (&result, "value of %s is %s", name, value);
  return result;
  }

4.6 Vprintf()函数

  int vprintf (const char *template, va_list ap)

  本函数跟printf函数很类似,只是将参数的数目可变的,变成了一个指针的列表。

4.7 Vfprintf()函数

  int vfprintf (FILE *stream, const char *template, va_list ap)

  本函数跟fprintf函数很类似,只是将参数的数目可变的,变成了一个指针的列表。

4.8 vfprintf()函数

  int vsprintf (char *s, const char *template, va_list ap)

  本函数跟sprintf函数很类似,只是将参数的数目可变的,变成了一个指针的列表。

4.9 vsnprintf()函数

  int vsnprintf (char *s, size_t size, const char *template, va_list ap)

  本函数跟snprintf函数很类似,只是将参数的数目可变的,变成了一个指针的列表。

4.10 vasprintf()函数

  int vasprintf (char **ptr, const char *template, va_list ap)

  本函数跟asprintf函数很类似,只是将参数的数目可变的,变成了一个指针的列表。

  当然,在GCC中还有别的格式化输出函数,以及格式化输入函数,我将在以后的文章中一一介绍(^_^.......)

2007年8月23日星期四

2007年8月21日星期二

Installing gcc as a cross-compiler

Installing gcc as a cross-compiler

A guide for an i686-pentium host and powerpc-linux target with altivec support



Created by Mauricio Alvarez
Revised by Friman Sánchez
Last revision: 19:39 (GMT) 07.10.2003

INDEX

1. Introduction
2. Preparing the installation
3. Required Packages
4. Building Binutils
5. Building a bootstrap gcc
6. Building the linux kernel
7. Building the runtime library
8. Building the final gcc cross-compiler
9. Building the GDB debugger
10. Using gcc with altivec extensions
11. References



1. Introduction.

The goal of this document is to describe the process of installing gcc 3.3.1 as a cross-compiler for a PowerPC target with altivec support on a Linux pentium host.

Mos of the information was taken from The Crossgcc Wiki, Crosstool web page and Cross Development mini-howto for PPC Linux

To do:
- Build the cross-development tools for a 64 bits PowerPC architecture (IBM 970)

A note about crosstool
I suggest the use of crosstool scripts because they automate the process of downloading, building, compiling, patching and installing gcc, binutils, glibc and linux kernel.

In order to use crosstool for a powerpc-linux-gnualtivec target just edit demo.sh script, comment all the procssor lines and create a new entry like this:

# PPC altivec
eval `cat powerpc-altivec.dat` `cat gcc3.3.1-glibc2.3.2.dat` sh all.sh --notest

File powerpc-altivec.dat has to be like that:

TARGET=powerpc-linux-gnualtivec
TARGET_CFLAGS="-O"
GCC_EXTRA_CONFIG="--enable-altivec"

But, if you want to do all the process by hand or you want to build a Newlib based compiler (not supported yet by crosstool) here is a step by step guide:

2. Preparing the installation

Important Note. For building a cross-development tools based on gcc-3.x it is necessary to have a recent version of gcc installed on the host workstation. Try to use the latest version (nowadays: gcc-3.3.1)

Select a target

For PowerPC there are some targets available:
from gcc installation manual
  • powerpc-linux: PowerPC based systems running linux OS and using Glibc runtime library
  • powerpc-elf: PowerPC big endian mode running System V.4
  • powerpc-eabi: Embedded PowerPC in big endian mode using Newlib runtime library
  • powerpc-eabisim: Embedded powerpc systems in big endian mode for use under PSIM/GDB simulator.
  • powerpc-eabialtivec: Embedded PowerPC with big endian mode using Newlib and automatic setting of altivec gcc switches.
  • powerpc-eabisimaltivec:
  • powerpc-linux-gnualtivec: PowerPC running linux and automatic setting of -maltivec and -mabi=altivec gcc switches.

In this guide we a re going to use the powerpc-linux-gnualtivec for systems running Linux and using Glibc and powerpc-eabialtivec for embedded systems using Newlib

$export TARGET=powerpc-linux-gnualtivec
or
$export TARGET=powerpc-eabisimaltivec


Suggested directories


Base directory: indentify processor, platform and OS
$mkdir i686-pc-linux-gnu
$ cd i
686-pc-linux-gnu
$mkdir cross-gcc
$ cd cross-gcc

Source code directory: to store tarballs
$mkdir tarballs
$export TARBALLS=
/i686-pc-linux-gnu/cross-gcc/tarballs

Build directories: to store specific configurations of tools.
$mkdir build/$TARGET

Final installation directory
$mkdir result/$TARGET
$export PREFIX=/i686-pc-linux-gnu/cross-gcc/result/{TARGET}
$export PATH=$PATH:${PREFIX}/bin

3. Required packages

binutils: cross-linker, cross-assembler and so on
current version: binutils-2.14.90.0.6
download from ftp://ftp.kernel.org/pub/linux/devel/binutils

gcc-core: basic C compiler
current stable version: gcc-core-3.3.1
download from (good europe gnu mirror site) ftp://ftp.lip6.fr/pub/gcc/releases/gcc-3.3.1/gcc-core-3.3.1.tar.gz

linux kernel headers: in order to build the final gcc and run time library for systems using Glibc
current stable version: 2.4.22
download from bkbits, more information on the penguinpcc kernel source page
or download it from Linus kernel three: ftp://ftp.kernel.org/pub/linux/kernel/v2.4/linux-2.4.22.tar.gz

Run time library:
Glibc:
C run time library for target platforms running linux
current version: glibc-2.3.2
and glibc-linuxthreads-2.3.2.tar.gz
download from: ftp://ftp.gnu.org/gnu/glibc

Newlib:
C run time library por embedded devices that does not run linux
current version: newlib-1.11.0
download from : ftp://sources.redhat.com/pub/newlib

Gnu Debugger GDB: it includes PSIM PowerPC ISA emulator
current version gdb-5.3
download it from: ftp://ftp.gnu.org/gnu/gdb

4. Building binutils

$ cd build/${TARGET}
$ tar xvfz ${TARBALLS}/binutils-2.10.90.0.6.tar.gz
$ mkdir build-binutils
$ cd build-binutils
$ ../binutils-2.14.90.0.6/configure --prefix=${PREFIX} --target=${TARGET}
$ make
$ make install

5. Building a bootstrap gcc

Build a simple C compiler that does not need kernel and libary headers in order to compile the run tine library. See a longer explanation about that in http://billgatliff.com/twiki/bin/view/Crossgcc/BuildBootstrapGcc

for gcc 3.3.1 is necessary to apply a patch.
download it from http://penguinppc.org/embedded/cross-compiling/gcc-3.3.1-crossppc.diff

$ cd build/${TARGET}
$ tar xvfz ${TARBALLS}/gcc-core-3.3.1.tar.gz
$ patch gcc-3.3.1/gcc/config/rs6000/linux.h gcc-3.3.1-crossppc.diff
$ mkdir build-bootstrap
$ cd build-bootstrap
$ ../gcc-3.3.1/configure
--target=${TARGET} --prefix=${PREFIX} --without-headers --with-newlib --with-gnu-as --with-gnu-ld --disable-shared --disable-threads
$ make all-gcc
$ make install-gcc

6. Building the Linux kernel

Only for systems using glibc abd only for obtaining linux headers

Obtain a PowerPC Linux kernel (using bk appliation and bkbits kernel tree). More information on penguinpcc kernel source page
Also is possible to obtain a kernel from kernel.org

$ cd build/{TARGET}
$ mkdir linuppc-2.4
$ bk clone bk://ppc.bkbits.net/linuxppc-2.4 linuxppc-2.4
$ cd linuxppc-2.4

edit Makefile and put
ARCH := ppc
CROSS_COMPILE = powerpc-linux-gnualtivec
$ make menuconfig (the default options are OK, compilation is only for headers not for the kernel itself)
$ make dep
$ make clean
$ make zImage


The Install the linux kernel headers
$ mkdir -p ${PREFIX}/${TARGET}/include
$ cp -r build/${TARGET}/linuxppc-2.4/include/linux ${PREFIX}/${TARGET}/include/linux
$ cp -r
build/${TARGET}/linuxppc-2.4/include/asm-ppc ${PREFIX}/${TARGET}/include/asm
$ cp -r
build/${TARGET}/linuxppc-2.4/include/asm-generic ${PREFIX}/${TARGET}/include/asm-generic

7. Building the runtime library.

7.1 For Glibc: systems running linux

there is a bug building glibc 2.3.2 with gcc 3.3.1
scanf.c in function sscanf
sscanf: 37 error ´va_start´ used in function with fixed args
in order to resolve it edit glibc-2.3.2/stdio-common/sscanf.c
and replace the declaration for sscanf to
int sscanf (const char *s, const char *format, ...)

$ cd build/${TARGET}
$ tar xvfz ${TARBALLS}/glibc-2.3.2.tar.gz
$cd glibc-2.3.2
$ tar xvfz
${TARBALLS}/glibc-linuxthreads-2.3.2.tar.gz
$ cd ..
$ mkdir build-glibc
$ cd build-glibc
$ BUILD_CC=gcc CC=${TARGET}-gcc AR=${TARGET}-ar RANLIB=${TARGET}-ranlib ../glibc-2.3.2/configure --prefix=$PREFIX --host=$TARGET --build=i686-pc-linux-gnu --disable-debug --disable-profile --enable-add-ons=linuxthreads --with-headers=${PREFIX}/${TARGET}/include
$make
$make install_root=${PREFIX}/${TARGET} prefix="" install
$ echo "GROUP ( libc.so.6 libc_nonshared.a )" > ${PREFIX}/${TARGET}/lib/libc.so
$ echo "GROUP (libpthread.so.0 libpthread_nonshared.a )" > ${PREFIX}/${TARGET}/lib/libpthread.so

(and may be do something with libgcc_s.so?)

7.2 Newlibs: embedded systems or cross-compiling for emulation in GDB

$ tar xzvf newlib-1.11.0.tar.gz
$ mkdir build-newlib && cd build-newlib
$ ../newlib-1.11.0/configure --target=$TARGET --prefix=$PREFIX
$ make all
$ make install


8. Building the final gcc cross-compiler

$ cd build/${TARGET}
$ mv gcc-3.3.1 gcc-core-3.3.1
$ tar xvfz ${TARBALLS}/gcc-3.3.1.tar.gz
$ patch gcc-3.3.1/gcc/config/rs6000/linux.h gcc-3.3.1-crossppc.diff
$ mkdir build-gcc
$ cd build-gcc
$ ../gcc-3.3.1/configure --target=${TARGET} --prefix=${PREFIX} --enable-languages=c,c++ --with-sysroot=${PREFIX}/${TARGET}/include --enable-altivec --with-local-prefix=${PREFIX}/${TARGET} --with-gnu-as --with-gnu-ld
$ make
$ make install


from gcc installation manual
--enable-altivec
Specify that the target supports AltiVec vector enhancements. This option will adjust the ABI for AltiVec enhancements, as well as generate AltiVec code when appropriate. This option is only available for PowerPC systems.


--with-cpu=cpu
Default value for mcpu compilation switch. Options for PowerPC can be:

Motorola PowerPC 7450 (MPC7450): --with-cpu=7450

Motorola PowerPC (taken from http://www.mot.com/powerpc)

Motorola G5: 8540, 8560 High-end SoCs
Motorola G4+ (Altivec Technology): 7451, 7455, 7457 (2003, 2Ghz)
Motorola G4 (Altivec Technology): 7400,7410,7450,7440
Motorola G3: 750, 740, 745, 755
Motorola G2: 603, 604, 8240, 8245, 8247(proposal), 8250/55, 8260/64/65/66 8270/75 8280 5xxx, 82xx,
Motorola G1: 601, 8xx, 5xx

6xx, 7xx, 7xxx: High performance microprocessor targeting computing and high-end embedded systems
8xx, 8xxx: Integrated processor targeting communications ad consumer markets
5xx, 5xxx: Integrated processor targeting transportation market.

IBM PowerPC
PowerPC 970: 64 bits High performance processor processor --with-cpu=????
PowerPC 750: High performance processor for mainstream desktops, high end workstations. --with-cpu=750
PowerPC 405: Embedded core for custom logic applications --with-cpu=405

    9. Build the debugger

    $ cd build/${TARGET}
    $ tar xvfz ${TRABALLS}/gdb-5.3.tar.gz
    $ mkdir build-gdb
    $ cd build-gdb
    $../gdb-5.3/configure --target=$TARGET --prefix=$PREFIX --enable-sim-powerpc
    $ make
    $ make install

    PSIM
    PSIM is an ISA emulator for PowerPC now included with GDB, it supports Altivec extensions when configured with a target like powerpc-eabisimaltivec and the --enable-sim-powerpc option.

    10. Using gcc with altivec extensions

    from gcc user manual

    -maltivec
    These switches enable or disable the use of built-in functions that allow access to the AltiVec instruction set. You may also need to set -mabi=altivec to adjust the current ABI with AltiVec ABI enhancements.


    -mabi=altivec
    Extend the current ABI with AltiVec ABI extensions. This does not change the default ABI, instead it adds the AltiVec ABI extensions to the current ABI.

    If you use a powerpc-*-gnualtivec target-maltivec and -mabi=altivec are set automatically by the cross-compiler


    Code Example
    Taken from Velocity Engine Tutorial http://developer.apple.com/hardware/ve/tutorial.html

    Download the code example 2comp.c
    #include 
    #include

    vector signed long vec_2sComp (vector signed long x);

    void main ()
    {
    vector signed long v1 = {-2,0,2,8};
    vector signed long v2;

    v2 = vec_2sComp (v1);

    printf ("Numbers %9.8vld\n", v1);
    printf ("2's Comp %9.8vld\n", v2);
    }


    vector signed long vec_2sComp (vector signed long x)
    {
    vector signed long one = {1,1,1,1};
    x = vec_nor (x, x);
    x = vec_add (x, one);
    return x;
    }

    Compiling altivec programs

    $powerpc-linux-gcc -maltivec -mabi=altivec -o 2comp 2comp.c

    Some notes about the GCC implementation of Altivec from GCC manual

    - It is necessary to include in the source code the library for using the altivec built-in funcitons n gcc 3.3
    #include 
    - When compiling it is necessary to add the -maltivec and -mabi=altivec gcc switches

    - Also there are some differences between the specification in Motorola's AltiVec Programming Interface Manual and gcc implementation:
    • Vector constants are done with braces {0,0,0,0} not with parentheses
    • Vector initializers require no casts if the vector constant is of the same type as the variable it is initializing
    • Vector initializers for constants require explicit declaration
      vector signed long one = (vector signed long) {1,1,1,1};
          vector signed long one = (vector signed long) {1}; generate an internal compiler error (gcc-3.3.1 bug)

    Simulating with GDB
    ( put something here)

    11. References

    GCC

    Building and Testing gcc/glibc cross toolchains
    http://www.kegel.com/crosstool/

    Cross Development mini-howto for PPC Linux
    http://penguinppc.org/embedded/cross-compiling/

    Crossgcc
    http://billgatliff.com/twiki/bin/view/Crossgcc/WebHome

    Building a GCC PowerPC/Linux Cross Compiler For SPARC/Solaris
    http://www.borg.umn.edu/~grant/Linux/cross.html

    Altivec

    Motorola Altivec manuals and source code examples
    http://www.mot.com/altivec

    Motorola Altivec Programming Interface Manual: High level (C and C++) programming
    http://e-www.motorola.com/files/32bit/doc/ref_manual/ALTIVECPIM.pdf

    Motorola Altivec Programming Environment Manual: Assembly programming
    http://e-www.motorola.com/files/32bit/doc/ref_manual/ALTIVECPEM.pdf

    Simd tech
    http://www.simdtech.org

    Mac developper connection: Velocity Engine
    http://developer.apple.com/hardware/ve/

    GCC笔记

    GCC笔记

    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
    #include
    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 ),消除了错误。

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

    搜索路径

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

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

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

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

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

    1, 如果使用非系统头文件, 头文件和源文件位于同一个目录, 如何引用头文件呢?
    ——我们可以简单地在源文件中使用 #include "file.h", gcc将当前目录的file.h引入到源文件. 如果你很固执, 仍想使用#include 语句, 可以在调用gcc时添加"-I."来将当前目录添加到系统包含路径. 细心的朋友可能会想到: 这样对引用其它头文件会不会有影响? 比如, #include之后紧接着一个#include, 它能正确引入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
    #include
    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时间.

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

    发表于: 2006-04-07,修改于: 2007-01-23 01:11 已浏览3388次,有评论7条 推荐 投诉

    网友评论
    joshua 时间:2006-04-12 14:27:11 IP地址:218.108.29.★


     不错,顶一个


    xiaolei 时间:2006-04-12 14:33:00 IP地址:222.40.15.★


    不错,顶解决问题的,顶一个


    zzzppp 时间:2006-04-13 20:42:22 IP地址:61.183.148.★


    多谢楼上两位夸奖!呵呵


    123 时间:2006-05-19 11:39:46 IP地址:61.183.148.★


    SHIT!


    本站网友 时间:2006-10-26 15:39:45 IP地址:194.138.203.★


    写着不错,顶!


    sealinger 时间:2006-12-21 11:09:39 IP地址:58.248.96.★


    好!


    本站网友 时间:2007-01-22 17:21:36 IP地址:218.6.195.★


    不错,看来我的博客也会成为你的又一个展示平台了,呵呵。

    2007年7月27日星期五

    gcc

    gcc

    目 录

    1. gcc
      1. makefile写法
      2. gcc_egcs使用
      3. gdb使用
      4. gcc常用选项对代码的影响
        1. 一般情况
        2. -O 编译选项
        3. -O2 编译选项
        4. -fomit-frame-pointer 编译选项
        5. -fomit-frame-pointer && -O2
        6. -fPIC 编译选项
        7. -static 编译选项
      5. AT&T的汇编格式
      6. x86内联汇编
        1. 简述
        2. 内联汇编
        3. 程序模板
        4. 操作数
        5. 修饰寄存器列表
        6. 操作数约束
        7. 示例
          1. 寄存器约束
          2. 匹配约束
          3. 内存操作数约束
          4. 修饰寄存器
      7. 不同的CPU下最佳编译参数
      8. 代码维护
        1. 简单cvs
        2. automake
        3. diff
        4. rcs
        5. 内核重编译常见故障
        6. cvs
        7. 共享库工具
        8. 代码优化
        9. GNU 编码标准
        10. 书籍


    gcc

    [目录]


    makefile写法

    蓝森林 http://www.lslnet.com 2001年3月22日 08:44

    作 者: 许明彦

    Abstract:

    在 Unix 上写程式的人大概都碰过 Makefile,尤其是用 C 来开发程式的人。用 make来开发和编译程式的确很方便,可是要写出一个 Makefile就不简单了。偏偏介绍 Makefile 的文件不多,GNU Make 那份印出来要几百页的文件,光看完 Overview 就快阵亡了,难怪许多
    人闻 Unix 色变。

    本文将介绍如何利用 GNU Autoconf 及 Automake 这两套软体来协助我们『自动』产生 Makefile 档,并且让开发出来的软体可以像 Apache, MySQL 和常见的 GNU 软体一样,只要会 ``./configure'', ``make'', ``make install'' 就可以把程式安装到系统中。如果您有
    心开发 Open Source 的软体,或只是想在 Unix 系统下写写程式。希望这份介绍文件能帮助您轻松地进入 Unix Programming 的殿堂。

    1. 简介

    Makefile 基本上就是『目标』(target), 『关连』(dependencies) 和『动作』三者所组成的一连串规则。而 make 就会根据 Makefile 的规则来决定如何编译 (compile) 和连结 (link) 程式。实际上,make 可做的不只是编译和连结程式,例如 FreeBSD 的 port collect
    ion 中, Makefile 还可以做到自动下载原始程式套件,解压缩 (extract) ,修补 (patch),设定,然後编译,安装至系统中。

    Makefile 基本构造虽然简单,但是妥善运用这些规则就也可以变出许多不同的花招。却也因此,许多刚开始学习写 Makefile 时会感到没有规范可循,每个人写出来的 Makefile 长得都不太一样,不知道从何下手,而且常常会受限於自己的开发环境,只要环境变数不同或路
    径改一下,可能Makefile 就得跟着修改。虽然有 GNU Makefile Conventions (GNU Makefile 惯例) 订出一些使用 GNU 程式设计时撰写 Makefile 的一些标准和规范,但是内容很长而且很复杂, 并且经常做些调整,为了减轻程式设计师维护 Makefile 的负担,因此有了Automake。

    程式设计师只需写一些预先定义好的巨集 (macro),交给 Automake 处理後会产生一个可供Autoconf 使用的 Makefile.in 档。再配合利用Autoconf 产生的自动设定档 configure即可产生一份符合 GNU Makefile惯例的 Makeifle 了。

    2. 上路之前

    在开始试着用 Automake 之前,请先确认你的系统已经安装以下的软体:
    1. GNU Automake
    2. GNU Autoconf
    3. GNU m4
    4. perl
    5. GNU Libtool (如果你需要产生 shared library)

    我会建议你最好也使用 GNU C/C++ 编译器 、GNU Make 以及其它 GNU 的工具程式来做为开发的环境,这些工具都是属於 Open Source Software不仅免费而且功能强大。如果你是使用Red Hat Linux 可以找到所有上述软体的 rpm 档,FreeBSD 也有现成的 package 可以直
    接安装,或着你也可以自行下载这些软体的原始档回来 DIY。以下的范例是在 Red Hat Linux 5.2 + CLE2 的环境下所完成的。

    3. 一个简单的例子

    Automake 所产生的 Makefile 除了可以做到程式的编译和连结,也已经把如何产生程式文件(如 manual page, info 档及 dvi 档) 的动作,还有把原始程式包装起来以供散 的动作都考虑进去了,所以原始程式所存放的目录架构最好符合 GNU 的标准惯例,接下来我拿hello.c 来做为例子。

    在工作目录下建立一个新的子目录 ``devel'',再在 devel 下建立一个``hello'' 的子目录,这个目录将作为我们存放 hello 这个程式及其相关档案的地方:

    % mkdir devel
    % cd devel
    % mkdir hello
    % cd hello

    用编辑器写个 hello.c 档,
    #include stdio.h
    int main(int argc, char** argv)
    {
    printf(``Hello, GNU! '');
    return 0;
    }

    接下来就要用 Autoconf 及 Automake 来帮我们产生 Makefile 档了,

    1. 用 autoscan 产生一个 configure.in 的雏型,执行 autoscan 後会产生一个configure.scan 的档案,我们可以用它做为configure.in档的蓝本。

    % autoscan
    % ls
    configure.scan hello.c

    2. 编辑 configure.scan 档,如下所示,并且把它的档名改成configure.in
    dnl Process this file with autoconf to produce a con figure script.
    AC_INIT(hello.c)
    AM_INIT_AUTOMAKE(hello, 1.0)
    dnl Checks for programs.
    AC_PROG_CC
    dnl Checks for libraries.
    dnl Checks for header files.
    dnl Checks for typedefs, structures, and compiler ch aracteristics.
    dnl Checks for library functions.
    AC_OUTPUT(Makefile)

    3. 执行 aclocal 和 autoconf ,分别会产生 aclocal.m4 及 configure 两个档案
    % aclocal
    % autoconf
    % ls
    aclocal.m4 configure configure.in hello.c

    4. 编辑 Makefile.am 档,内容如下
    AUTOMAKE_OPTIONS= foreign
    bin_PROGRAMS= hello
    hello_SOURCES= hello.c

    5. 执行 automake --add-missing ,Automake 会根据 Makefile.am 档产生一些档案,包含最重要的 Makefile.in
    % automake --add-missing
    automake: configure.in: installing `./install-sh'
    automake: configure.in: installing `./mkinstalldirs'
    automake: configure.in: installing `./missing'

    6. 最後执行 ./configure ,
    % ./configure
    creating cache ./config.cache
    checking for a BSD compatible install... /usr/bin/in stall -c
    checking whether build environment is sane... yes
    checking whether make sets ${MAKE}... yes
    checking for working aclocal... found
    checking for working autoconf... found
    checking for working automake... found
    checking for working autoheader... found
    checking for working makeinfo... found
    checking for gcc... gcc
    checking whether the C compiler (gcc ) works... yes
    checking whether the C compiler (gcc ) is a cross-co mpiler... no
    checking whether we are using GNU C... yes
    checking whether gcc accepts -g... yes
    updating cache ./config.cache
    creating ./config.status
    creating Makefile

    现在你的目录下已经产生了一个 Makefile 档,下个 ``make'' 指令就可以开始编译 hello.c 成执行档,执行 ./hello 和 GNU 打声招呼吧!

    % make
    gcc -DPACKAGE="hello" -DVERSION="1.0" -I. -I. -g -O2 -c he llo.c
    gcc -g -O2 -o hello hello.o
    % ./hello
    Hello! GNU!

    你还可以试试 ``make clean'',''make install'',''make dist'' 看看会有什麽结果。你也可以把产生出来的 Makefile 秀给你的老板,让他从此对你刮目相看 :-)

    4. 一探究竟

    上述产生 Makefile 的过程和以往自行编写的方式非常不一样,舍弃传统自行定义 make 的规则,使用 Automake 只需用到一些已经定义好的巨集即可。我们把巨集及目标 (target)写在 Makefile.am 档内,Automake读入 Makefile.am 档後会把这一串已经定义好的巨集展
    开并且产生对应的Makefile.in 档, 然後再由 configure 这个 shell script 根据Makefile.in 产生适合的 Makefile。
    在此范例中可藉由 Autoconf 及 Automake 工具所产生的档案有 configure.scan、aclocal.m4、configure、Makefile.in,需要我们加入设定者为 configure.in 及 Makefile.am。

    4.1 编辑 configure.in 档

    Autoconf 是用来产生 'configure' 档的工具。'configure' 是一个shell script,它可以自动设定原始程式以符合各种不同平台上 Unix 系统的特性,并且根据系统叁数及环境产生合适的 Makefile 档或是C 的标头档 (header file),让原始程式可以很方便地在这些不同
    的平台上被编译出来。Autoconf 会读取 configure.in 档然後产生 'configure' 这个shell script。

    configure.in 档的内容是一连串 GNU m4 的巨集,这些巨集经过autoconf 处理後会变成检查系统特徵的 shell script。configure.in 内巨集的顺序并没有特别的规定,但是每一个 configure.in 档必须在所有巨集前加入 AC_INIT 巨集,然後在所有巨集的最後面加上 AC
    _OUTPUT 巨集。我们可先用 autoscan 扫描原始档以产生一个 configure.scan 档,再对 configure.scan 做些修改成 configure.in 档。在范例中所用到的巨集如下:

    dnl
    这个巨集後面的字不会被处理,可视为注解。

    AC_INIT(FILE)
    这个巨集用来检查原始码所在的路径,autoscan 会自动产生,我们不必修改它。

    AM_INIT_AUTOMAKE(PACKAGE,VERSION)
    这是使用 Automake 所必备的巨集,PACKAGE 是我们所要产生软体套件的名称,VERSION 是版本编号。

    AC_PROG_CC
    检查系统可用的 C 编译器,如果原始程式是用 C 写的就需要这个巨集。

    AC_OUTPUT(FILE)
    设定 configure 所要产生的档案,如果是 Makefile 的话,configure 便会把它检查出来的结果带入 Makefile.in 档然後产生
    合适的 Makefile。

    实际上,我们使用 Automake 时,还须要一些其它的巨集,这些额外的巨集我们用 aclocal来帮我们产生。执行 aclocal 会产生 aclocal.m4档,如果没有特别的用途,我们可以不必修改它,用 aclocal 所产生的巨集会告诉 Automake 怎麽做。

    有了 configure.in 及 aclocal.m4 两个档案後,便可以执行 autoconf来产生 configure档了。

    4.2 编辑 Makefile.am 档

    接下来我们要编辑 Makefile.am 档,Automake 会根据 configure.in 中的巨集把Makefile.am 转成 Makefile.in 档。Makefile.am 档定义我们所要产的目标:

    AUTOMAKE_OPTIONS
    设定 automake 的选项。Automake 主要是帮助开发 GNU 软体的人员维护软体套件,所以在执行 automake 时,会检查目录下是否存在标准 GNU 软体套件中应具备的文件档案,例如 'NEWS'、'AUTHOR'、'ChangeLog' 等文件档。设成 foreign 时,automake 会改用一般软
    体套件的标准来检查。

    bin_PROGRAMS
    定义我们所要产生的执行档档名。如果要产生多个执行档,每个档名用空白字元隔开。

    hello_SOURCES
    定义 'hello' 这个执行档所需要的原始档。如果 'hello' 这个程式是由多个原始档所产生,必须把它所用到的原始档都列出来,以空白字元隔开。假设 'hello' 这个程式需要 'hello.c'、'main.c'、

    'hello.h'
    三个档案的话,则定义
    hello_SOURCES= hello.c main.c hello.h
    如果我们定义多个执行档,则对每个执行档都要定义相对的filename_SOURCES。

    编辑好 Makefile.am 档,就可以用 automake --add-missing 来产生Makefile.in。加上 --add-missing 选项是告诉 automake 顺便帮我们加入包装一个软体套件所必备的档案。Automake 产生出来的 Makefile.in档是完全符合 GNU Makefile 的惯例,我们只要执行 confi
    gure 这个shell script 便可以产生合适的 Makefile 档了。

    4.3 使用 Makefile

    利用 configure 所产生的 Makefile 档有几个预设的目标可供使用,我们只拿其中几个简述如下:

    make all
    产生我们设定的目标,即此范例中的执行档。只打 make 也可以,此时会开始编译原始码,然後连结,并且产生执行档。

    make clean
    清除之前所编译的执行档及目的档 (object file, *.o)。

    make distclean
    除了清除执行档和目的档外,也把 configure 所产生的 Makefile也清除掉。

    make install
    将程式安装至系统中。如果原始码编译无误,且执行结果正确,便可以把程式安装至系统预设的执行档存放路径。如果我们用bin_PROGRAMS 巨集的话,程式会被安装至 /usr/local/bin 这个目录。

    make dist
    将程式和相关的档案包装成一个压缩档以供散播 (distribution) 。执行完在目录下会产生一个以 PACKAGE-VERSION.tar.gz 为名称的档案。PACKAGE 和 VERSION 这两个变数是根据 configure.in 档中AM_INIT_AUTOMAKE(PACKAGE, VERSION) 的定义。在此范例中会产生
    'hello-1.0.tar.gz' 的档案。

    make distcheck
    和 make dist 类似,但是加入检查包装後的压缩档是否正常。这个目标除了把程式和相关档案包装成 tar.gz 档外,还会自动把这个压
    缩档解开,执行 configure,并且进行 make all 的动作,确认编译无误後,会显示这个 tar.gz 档已经准备好可供散播了。这个检查非
    常有用,检查过关的套件,基本上可以给任何一个具备 GNU 发展境的人去重新编译。就 hello-1.tar.gz 这个范例而言,除了在 Red
    Hat Linux 上,在 FreeBSD 2.2.x 版也可以正确地重新编译。

    要注意的是,利用 Autoconf 及 Automake 所产生出来的软体套件是可以在没有安装 Autoconf 及 Automake 的环境上使用的,因为 configure 是一个 shell script,它己被设计可以在一般 Unix 的 sh 这个 shell 下执行。但是如果要修改 configure.in 及 Makefile.a
    m 档再产生新的configure 及 Makefile.in 档时就一定要有 Autoconf 及 Automake 了。

    5. 相关讯息

    Autoconf 和 Automake 功能十分强大,你可以从它们所附的 info 档找到详细的用法。你也可以从许多现存的 GNU 软体或 Open Source 软体中找到相关的 configure.in 或 Makefile.am 档,它们是学习 Autoconf 及Automake 更多技巧的最佳范例。

    这篇简介只用到了 Autoconf 及 Automake 的皮毛罢了,如果你有心加入Open Source 软体开发的行列,希望这篇文件能帮助你对产生 Makefile有个简单的依据。其它有关开发 GNU程式或 C 程式设计及 Makefile 的详细运用及技巧,我建议你从 GNU Coding Standards3
    (GNU 编码标准规定) 读起,里面包含了 GNU Makefile 惯例,还有发展 GNU 软体套件的标准程序和惯例。这些 GNU 软体的线上说明文件可以在http://www.gnu.org/ 这个网站上找到。

    6. 结语

    经由 Autoconf 及 Automake 的辅助,产生一个 Makefile 似乎不再像以前那麽困难了,而使用 Autoconf 也使得我们在不同平台上或各家 Unix之间散播及编译程式变得简单,这对於在 Unix 系统上开发程式的人员来说减轻了许多负担。妥善运用这些 GNU 的工具软体,可
    以帮助我们更容易去发展程式,而且更容易维护原始程式码。

    一九九八年是 Open Source 运动风起云涌的一年,许多 Open Source 的软体普遍受到网路上大众的欢迎和使用。感谢所有为 Open Source 奉献的人们,也希望藉由本文能吸引更多的人加入『自由』、『开放』的 OpenSource 行列。

    About this document ...

    轻轻松松产生 Makefile1

    This document was generated using the LaTeX2HTML translator Version 98.2 beta6 (August 14th, 1998) Copyright (C) 1993, 1994, 1995, 1996, Nikos Drakos, ComputerBased Learning Unit, University of Leeds.Copyright (C) 1997, 1998, Ross Moore, Mathematics Department,Macquarie University, Sydney.

    The command line arguments were:
    latex2html -split 0 -show_section_numbers automake.tex
    The translation was initiated by on 1999-02-11

    Footnotes
    ... itle1
    本文件使用 ChiLaTeX 制作。
    ... CLE2
    CLE (Chinese Linux Extension,Linux 中文延伸套件),
    http://cle.linux.org.tw/

    ... Standards3
    GNU Coding Standards, Richard Stallman.

    [目录]


    gcc_egcs使用

    1.使用egcs

    Linux 中最重要的软件开发工具是 GCC。GCC 是 GNU 的 C 和 C++ 编译器。实际上,GCC 能够编译三种语言:C、C++ 和 Object C(C 语言的一种面向对象扩展)。利用 gcc 命令可同时编译并连接 C 和 C++ 源程序。

    #DEMO#: hello.c

    如果你有两个或少数几个 C 源文件,也可以方便地利用 GCC 编译、连接并生成可执行文件。例如,假设你有两个源文件 main.c 和 factorial.c 两个源文件,现在要编译生成一个计算阶乘的程序。

    -----------------------
    清单 factorial.c
    -----------------------
    #include
    #include

    int factorial (int n)
    {
    if (n <= 1)
    return 1;

    else
    return factorial (n - 1) * n;
    }
    -----------------------

    -----------------------
    清单 main.c
    -----------------------
    #include
    #include

    int factorial (int n);

    int main (int argc, char **argv)
    {
    int n;

    if (argc < 2) {
    printf ("Usage: %s n\n", argv [0]);
    return -1;
    }
    else {
    n = atoi (argv[1]);
    printf ("Factorial of %d is %d.\n", n, factorial (n));
    }

    return 0;
    }
    -----------------------

    利用如下的命令可编译生成可执行文件,并执行程序:

    $ gcc -o factorial main.c factorial.c
    $ ./factorial 5
    Factorial of 5 is 120.

    GCC 可同时用来编译 C 程序和 C++ 程序。一般来说,C 编译器通过源文件的后缀名来判断是 C 程序还是 C++ 程序。在 Linux 中,C 源文件的后缀名为 .c,而 C++ 源文件的后缀名为 .C 或 .cpp。

    但是,gcc 命令只能编译 C++ 源文件,而不能自动和 C++ 程序使用的库连接。因此,通常使用 g++ 命令来完成 C++ 程序的编译和连接,该程序会自动调用 gcc 实现编译。假设我们有一个如下的 C++ 源文件(hello.C):

    #include

    void main (void)
    {
    cout << "Hello, world!" << endl;
    }

    则可以如下调用 g++ 命令编译、连接并生成可执行文件:

    $ g++ -o hello hello.C
    $ ./hello
    Hello, world!

    2. gcc/egcs 的主要选项

    表 1-3 gcc 命令的常用选项
    选项 解释
    -ansi 只支持 ANSI 标准的 C 语法。这一选项将禁止 GNU C 的某些特色,
    例如 asm 或 typeof 关键词。
    -c 只编译并生成目标文件。
    -DMACRO 以字符串“1”定义 MACRO 宏。
    -DMACRO=DEFN 以字符串“DEFN”定义 MACRO 宏。
    -E 只运行 C 预编译器。
    -g 生成调试信息。GNU 调试器可利用该信息。
    -IDIRECTORY 指定额外的头文件搜索路径DIRECTORY。
    -LDIRECTORY 指定额外的函数库搜索路径DIRECTORY。
    -lLIBRARY 连接时搜索指定的函数库LIBRARY。
    -m486 针对 486 进行代码优化。
    -o FILE 生成指定的输出文件。用在生成可执行文件时。
    -O0 不进行优化处理。
    -O 或 -O1 优化生成代码。
    -O2 进一步优化。
    -O3 比 -O2 更进一步优化,包括 inline 函数。
    -shared 生成共享目标文件。通常用在建立共享库时。
    -static 禁止使用共享连接。
    -UMACRO 取消对 MACRO 宏的定义。
    -w 不生成任何警告信息。
    -Wall 生成所有警告信息。

    #DEMO#

    [目录]


    gdb使用

    1.简介

    GNU 的调试器称为 gdb,该程序是一个交互式工具,工作在字符模式。在 X Window 系统中,有一个 gdb 的前端图形工具,称为 xxgdb。gdb 是功能强大的调试程序,可完成如下的调试任务:

    * 设置断点;
    * 监视程序变量的值;
    * 程序的单步执行;
    * 修改变量的值。
    在可以使用 gdb 调试程序之前,必须使用 -g 选项编译源文件。可在 makefile 中如下定义CFLAGS 变量:

    CFLAGS = -g
    运行 gdb 调试程序时通常使用如下的命令:

    gdb progname

    在 gdb 提示符处键入help,将列出命令的分类,主要的分类有:

    * aliases:命令别名
    * breakpoints:断点定义;
    * data:数据查看;
    * files:指定并查看文件;
    * internals:维护命令;
    * running:程序执行;
    * stack:调用栈查看;
    * statu:状态查看;
    * tracepoints:跟踪程序执行。

    键入 help 后跟命令的分类名,可获得该类命令的详细清单。

    2.gdb 的常用命令
    表 1-4 常用的 gdb 命令
    命令 解释
    break NUM 在指定的行上设置断点。
    bt 显示所有的调用栈帧。该命令可用来显示函数的调用顺序。
    clear 删除设置在特定源文件、特定行上的断点。其用法为:clear FILENAME:NUM。
    continue 继续执行正在调试的程序。该命令用在程序由于处理信号或断点而
    导致停止运行时。
    display EXPR 每次程序停止后显示表达式的值。表达式由程序定义的变量组成。
    file FILE 装载指定的可执行文件进行调试。
    help NAME 显示指定命令的帮助信息。
    info break 显示当前断点清单,包括到达断点处的次数等。
    info files 显示被调试文件的详细信息。
    info func 显示所有的函数名称。
    info local 显示当函数中的局部变量信息。
    info prog 显示被调试程序的执行状态。
    info var 显示所有的全局和静态变量名称。
    kill 终止正被调试的程序。
    list 显示源代码段。
    make 在不退出 gdb 的情况下运行 make 工具。
    next 在不单步执行进入其他函数的情况下,向前执行一行源代码。
    print EXPR 显示表达式 EXPR 的值。

    3.gdb 使用范例

    -----------------
    清单 一个有错误的 C 源程序 bugging.c
    -----------------
    #include
    #include

    static char buff [256];
    static char* string;
    int main ()
    {

    printf ("Please input a string: ");
    gets (string);

    printf ("\nYour string is: %s\n", string);
    }
    -----------------

    上面这个程序非常简单,其目的是接受用户的输入,然后将用户的输入打印出来。该程序使用了一个未经过初始化的字符串地址 string,因此,编译并运行之后,将出现 Segment Fault 错误:

    $ gcc -o test -g test.c
    $ ./test
    Please input a string: asfd
    Segmentation fault (core dumped)

    为了查找该程序中出现的问题,我们利用 gdb,并按如下的步骤进行:

    1.运行 gdb bugging 命令,装入 bugging 可执行文件;
    2.执行装入的 bugging 命令;
    3.使用 where 命令查看程序出错的地方;
    4.利用 list 命令查看调用 gets 函数附近的代码;
    5.唯一能够导致 gets 函数出错的因素就是变量 string。用 print 命令查看 string 的值;
    6.在 gdb 中,我们可以直接修改变量的值,只要将 string 取一个合法的指针值就可以了,为
    此,我们在第 11 行处设置断点;
    7.程序重新运行到第 11 行处停止,这时,我们可以用 set variable 命令修改 string 的取值;
    8.然后继续运行,将看到正确的程序运行结果。

    [目录]


    gcc常用选项对代码的影响

    by alert7
    2001-12-21
    测试环境 redhat 6.2

    ★ 前言
    本文讨论gcc的一些常用编译选项对代码的影响。当然代码变了,它的内存布局也就会变了,随之exploit也就要做相应的变动。
    gcc的编译选项实在太多,本文检了几个最常用的选项。

    ★ 演示程序
    [alert7@redhat62 alert7]$ cat > test.c
    #include
    void hi(void)
    {
    printf("hi");
    }
    int main(int argc, char *argv[])
    {
    hi();
    return 0;
    }

    [目录]


    一般情况

    ★ 一般情况
    [alert7@redhat62 alert7]$ gcc -o test test.c
    [alert7@redhat62 alert7]$ wc -c test
    11773 test
    [alert7@redhat62 alert7]$ gdb -q test
    (gdb) disass main
    Dump of assembler code for function main:
    0x80483e4 : push %ebp
    0x80483e5 : mov %esp,%ebp
    0x80483e7 : call 0x80483d0
    0x80483ec : xor %eax,%eax
    0x80483ee : jmp 0x80483f0
    0x80483f0 : leave
    0x80483f1 : ret
    ....
    End of assembler dump.
    (gdb) disass hi
    Dump of assembler code for function hi:
    0x80483d0 : push %ebp
    0x80483d1 : mov %esp,%ebp
    0x80483d3 : push $0x8048450
    0x80483d8 : call 0x8048308
    0x80483dd : add $0x4,%esp
    0x80483e0 : leave
    0x80483e1 : ret
    0x80483e2 : mov %esi,%esi
    End of assembler dump.
    来看看部分的内存映象
    (内存高址)
    +--------+
    |bffffbc4| argv的地址(即argv[0]的地址)
    0xbffffb84 +--------+
    |00000001| argc的值
    0xbffffb80 +--------+
    |400309cb|main的返回地址
    0xbffffb7c +--------+ <-- 调用main函数前的esp
    |bffffb98| 调用main函数前的ebp
    0xbffffb78 +--------+ <-- main函数的ebp
    |080483ec| hi()的返回地址
    0xbffffb74 +--------+
    |bffffb78| 调用hi()前的esp
    0xbffffb70 +--------+
    |08048450| "hi"的地址
    0xbffffb6c +--------+
    | ...... |
    (内存低址)
    leave 指令所做的操作相当于MOV ESP,EBP 然后 POP EBP
    ret 指令所做的操作相当于POP EIP

    [目录]


    -O 编译选项

    ★ -O 编译选项
    With `-O', the compiler tries to reduce code size and execution time.
    When you specify `-O', the two options `-fthread-jumps' and
    `-fdefer-pop' are turned on
    优化,减少代码大小和执行的时间
    [alert7@redhat62 alert7]$ gcc -O -o test test.c
    [alert7@redhat62 alert7]$ wc -c test
    11757 test
    [alert7@redhat62 alert7]$ gdb -q test
    (gdb) disass main
    Dump of assembler code for function main:
    0x80483d8 : push %ebp
    0x80483d9 : mov %esp,%ebp
    0x80483db : call 0x80483c8
    0x80483e0 : xor %eax,%eax
    0x80483e2 : leave
    0x80483e3 : ret
    0x80483e4 : nop
    ...
    End of assembler dump.
    (gdb) disass hi
    Dump of assembler code for function hi:
    0x80483c8 : push %ebp
    0x80483c9 : mov %esp,%ebp
    0x80483cb : push $0x8048440
    0x80483d0 : call 0x8048308
    0x80483d5 : leave
    0x80483d6 : ret
    0x80483d7 : nop
    End of assembler dump.

    在main()中,把一条jmp指令优化掉了,很显然,这条指令是可以不需要的。
    在hi()中,把add $0x4,%esp优化掉了,这会不会使stack不平衡呢?

    来看看部分的内存映象

    (内存高址)
    +--------+
    |bffffbc4| argv的地址(即argv[0]的地址)
    0xbffffb84 +--------+
    |00000001| argc的值
    0xbffffb80 +--------+
    |400309cb|main的返回地址
    0xbffffb7c +--------+ <-- 调用main函数前的esp
    |bffffb98| 调用main函数前的ebp
    0xbffffb78 +--------+ <-- main函数的ebp
    |080483e0| hi()的返回地址
    0xbffffb74 +--------+
    |bffffb78| 调用hi()前的esp
    0xbffffb70 +--------+
    |08048440| "hi"的地址
    0xbffffb6c +--------+
    | ...... |
    (内存低址)
    leave指令所做的操作相当于把MOV ESP,EBP 然后 POP EBP。看到leave指令操作了没有,先把ebp-->esp,再pop ebp,这样即使在过程内堆栈的esp,ebp是不平衡的,但只要返回时候碰到leave指令就会平衡了,所以把add $0x4,%esp优化掉也是没有问题的。


    [目录]


    -O2 编译选项

    ★ -O2 编译选项
    -O2
    Optimize even more. Nearly all supported optimizations that do
    not involve a space-speed tradeoff are performed. Loop unrolling
    and function inlining are not done, for example. As compared to -O,
    this option increases both compilation time and the performance of
    the generated code.
    [alert7@redhat62 alert7]$ gcc -O2 -o test test.c
    [alert7@redhat62 alert7]$ wc -c test
    11757 test
    [alert7@redhat62 alert7]$ gdb -q test
    (gdb) disass main
    Dump of assembler code for function main:
    0x80483d8 : push %ebp
    0x80483d9 : mov %esp,%ebp
    0x80483db : call 0x80483c8
    0x80483e0 : xor %eax,%eax
    0x80483e2 : leave
    0x80483e3 : ret
    ...
    0x80483ef : nop
    End of assembler dump.
    (gdb) disass hi
    Dump of assembler code for function hi:
    0x80483c8 : push %ebp
    0x80483c9 : mov %esp,%ebp
    0x80483cb : push $0x8048440
    0x80483d0 : call 0x8048308
    0x80483d5 : leave
    0x80483d6 : ret
    0x80483d7 : nop
    End of assembler dump.
    由于程序比较简单,再优化也没有好优化的了,所以跟-O出来的一样。


    [目录]


    -fomit-frame-pointer 编译选项

    ★ -fomit-frame-pointer 编译选项
    -fomit-frame-pointer
    Don't keep the frame pointer in a register for functions
    that don't need one. This avoids the instructions to save,
    set up and restore frame pointers; it also makes an extra
    register available in many functions. It also makes
    debugging impossible on most machines.

    忽略帧指针。这样在程序就不需要保存,安装,和恢复ebp了。这样ebp也就是一个free的register了,在函数中就可以随便使用了。

    [alert7@redhat62 alert7]$ gcc -fomit-frame-pointer -o test test.c
    [alert7@redhat62 alert7]$ wc -c test
    11773 test
    [alert7@redhat62 alert7]$ gdb -q test
    (gdb) disass main
    Dump of assembler code for function main:
    0x80483e0 : call 0x80483d0
    0x80483e5 : xor %eax,%eax
    0x80483e7 : jmp 0x80483f0
    0x80483e9 : lea 0x0(%esi,1),%esi
    0x80483f0 : ret
    ....
    End of assembler dump.
    (gdb) disass hi
    Dump of assembler code for function hi:
    0x80483d0 : push $0x8048450
    0x80483d5 : call 0x8048308
    0x80483da : add $0x4,%esp
    0x80483dd : ret
    0x80483de : mov %esi,%esi
    End of assembler dump.
    在main()和hi()中都去掉了以下指令
    push %ebp
    mov %esp,%ebp//这两条指令安装
    leave//这条指令恢复
    来看看部分的内存映象
    (内存高址)
    +--------+
    |bffffbc4| argv的地址(即argv[0]的地址)
    0xbffffb84 +--------+
    |00000001| argc的值
    0xbffffb80 +--------+
    |400309cb|main的返回地址
    0xbffffb7c +--------+
    |080483e5| hi()的返回地址
    0xbffffb78 +--------+
    |08048450| "hi"字符串的地址
    0xbffffb74 +--------+
    | ...... |
    (内存低址)
    没有保存上层执行环境的ebp.

    [目录]


    -fomit-frame-pointer && -O2

    ★ -fomit-frame-pointer && -O2
    -fomit-frame-pointer编译选项去掉了
    push %ebp
    mov %esp,%ebp//这两条指令安装
    leave//这条指令恢复
    -O2编译选项去掉了
    add $0x4,%esp
    两个加起来会不会这四条指令一起去掉,从而使stack不平衡呢?
    [alert7@redhat62 alert7]$ gcc -fomit-frame-pointer -O2 -o test test.c
    [alert7@redhat62 alert7]$ wc -c test
    11741 test
    [alert7@redhat62 alert7]$ gdb -q test
    (gdb) disass main
    Dump of assembler code for function main:
    0x80483d8 : call 0x80483c8
    0x80483dd : xor %eax,%eax
    0x80483df : ret
    End of assembler dump.
    (gdb) disass hi
    Dump of assembler code for function hi:
    0x80483c8 : push $0x8048430
    0x80483cd : call 0x8048308
    0x80483d2 : add $0x4,%esp
    0x80483d5 : ret
    0x80483d6 : mov %esi,%esi
    End of assembler dump.
    来看看部分的内存映象
    (内存高址)
    +--------+
    |bffffbc4| argv的地址(即argv[0]的地址)
    0xbffffb84 +--------+
    |00000001| argc的值
    0xbffffb80 +--------+
    |400309cb|main的返回地址
    0xbffffb7c +--------+
    |080483dd| hi()的返回地址
    0xbffffb78 +--------+
    |08048430| "hi"字符串的地址
    0xbffffb74 +--------+
    | ...... |
    (内存低址)
    此时就没有把add $0x4,%esp优化掉,如果优化掉的话,整个stack就
    会变的不平衡,从而会导致程序出错。

    [目录]


    -fPIC 编译选项

    ★ -fPIC 编译选项
    -fPIC If supported for the target machine, emit position-independent
    code, suitable for dynamic linking,even if branches need large
    displacements.

    产生位置无关代码(PIC),一般创建共享库时用到。
    在x86上,PIC的代码的符号引用都是通过ebx进行操作的。

    [alert7@redhat62 alert7]$ gcc -fPIC -o test test.c
    [alert7@redhat62 alert7]$ wc -c test
    11805 test
    [alert7@redhat62 alert7]$ gdb -q test
    (gdb) disass main
    Dump of assembler code for function main:
    0x80483f8 : push %ebp
    0x80483f9 : mov %esp,%ebp
    0x80483fb : push %ebx
    0x80483fc : call 0x8048401
    0x8048401 : pop %ebx//取得该指令的地址
    0x8048402 : add $0x1093,%ebx//此时ebx里面存放着是GOT表的地址
    0x8048408 : call 0x80483d0
    0x804840d : xor %eax,%eax
    0x804840f : jmp 0x8048411
    0x8048411 : mov 0xfffffffc(%ebp),%ebx
    0x8048414 : leave
    0x8048415 : ret
    ...
    End of assembler dump.
    (gdb) disass hi
    Dump of assembler code for function hi:
    0x80483d0 : push %ebp
    0x80483d1 : mov %esp,%ebp
    0x80483d3 : push %ebx
    0x80483d4 : call 0x80483d9
    0x80483d9 : pop %ebx
    0x80483da : add $0x10bb,%ebx
    0x80483e0 : lea 0xffffefdc(%ebx),%edx
    0x80483e6 : mov %edx,%eax
    0x80483e8 : push %eax
    0x80483e9 : call 0x8048308
    0x80483ee : add $0x4,%esp
    0x80483f1 : mov 0xfffffffc(%ebp),%ebx
    0x80483f4 : leave
    0x80483f5 : ret
    0x80483f6 : mov %esi,%esi
    End of assembler dump.
    来看看部分的内存映象

    (内存高址)
    +--------+
    |bffffbc4| argv的地址(即argv[0]的地址)
    0xbffffb84 +--------+
    |00000001| argc的值
    0xbffffb80 +--------+
    |400309cb|main的返回地址
    0xbffffb7c +--------+ <-- 调用main函数前的esp
    |bffffb98| 调用main函数前的ebp
    0xbffffb78 +--------+ <-- main函数的ebp
    |401081ec| 保存的ebx
    0xbffffb74 +--------+
    |0804840d| (存放过call 0x8048401的下一条指令地址)
    0xbffffb70 +--------+
    |bffffb78| 调用hi()前的esp
    0xbffffb6c +--------+
    |08049494| GOT表地址
    0xbffffb68 +--------+
    |08048470|(存放过call 0x80483d9的下一条指令地址)
    0xbffffb64 +--------+
    | ...... |
    (内存低址)

    [目录]


    -static 编译选项

    ★ -static 编译选项
    -static
    On systems that support dynamic linking, this prevents
    linking with the shared libraries. On other systems,
    this option has no effect.
    把一些函数都静态的编译到程序中,而无需动态链接了。
    [alert7@redhat62 alert7]$ gcc -o test -static test.c
    [alert7@redhat62 alert7]$ wc -c test
    962808 test
    [alert7@redhat62 alert7]$ gdb -q test
    (gdb) disass main
    Dump of assembler code for function main:
    0x80481b4 : push %ebp
    0x80481b5 : mov %esp,%ebp
    0x80481b7 : call 0x80481a0
    0x80481bc : xor %eax,%eax
    0x80481be : jmp 0x80481c0
    0x80481c0 : leave
    0x80481c1 : ret
    ...
    End of assembler dump.
    (gdb) disass hi
    Dump of assembler code for function hi:
    0x80481a0 : push %ebp
    0x80481a1 : mov %esp,%ebp
    0x80481a3 : push $0x8071528
    0x80481a8 : call 0x804865c
    0x80481ad : add $0x4,%esp
    0x80481b0 : leave
    0x80481b1 : ret
    0x80481b2 : mov %esi,%esi
    End of assembler dump.
    [alert7@redhat62 alert7]$ ldd test

    not a dynamic executable
    -static出来的代码已经没有PLT了,GOT虽然有,已经全部为0了。

    [目录]


    AT&T的汇编格式

    一 基本语法

    语法上主要有以下几个不同.

    ★ 寄存器命名原则

    AT&T: %eax Intel: eax

    ★源/目的操作数顺序

    AT&T: movl %eax,%ebx Intel: mov ebx,eax

    ★常数/立即数的格式

    AT&T: movl $_value,%ebx Intel: mov eax,_value
    把_value的地址放入eax寄存器

    AT&T: movl $0xd00d,%ebx Intel: mov ebx,0xd00d

    ★ 操作数长度标识

    AT&T: movw %ax,%bx Intel: mov bx,ax

    ★寻址方式

    AT&T: immed32(basepointer,indexpointer,indexscale)
    Intel: [basepointer + indexpointer*indexscale + imm32)

    Linux工作于保护模式下,用的是32位线性地址,所以在计算地址时不用考虑segment:offset的问题.上式中的地址应为:
    imm32 + basepointer + indexpointer*indexscale

    下面是一些例子:

    ★直接寻址

    AT&T: _booga ; _booga是一个全局的C变量

    注意加上$是表示地址引用,不加是表示值引用.
    注:对于局部变量,可以通过堆栈指针引用.

    Intel: [_booga]

    ★寄存器间接寻址

    AT&T: (%eax)
    Intel: [eax]

    ★变址寻址

    AT&T: _variable(%eax)
    Intel: [eax + _variable]

    AT&T: _array(,%eax,4)
    Intel: [eax*4 + _array]

    AT&T: _array(%ebx,%eax,8)
    Intel: [ebx + eax*8 + _array]

    二 基本的行内汇编

    ·基本的行内汇编很简单,一般是按照下面的格式:
    asm("statements");
    例如:asm("nop"); asm("cli");

    ·asm 和 __asm__是完全一样的.

    ·如果有多行汇编,则每一行都要加上 "\n\t"
    例如:

    asm( "pushl %eax\n\t"
    "movl $0,%eax\n\t"
    "popl %eax");

    实际上gcc在处理汇编时,是要把asm(...)的内容"打印"到汇编文件中,所以格式控制字符是必要的.

    再例如:
    asm("movl %eax,%ebx");
    asm("xorl %ebx,%edx");
    asm("movl $0,_booga);

    在上面的例子中,由于我们在行内汇编中改变了edx和ebx的值,但是由于gcc的特殊的处理方法,即先形成汇编文件,再交给GAS去汇编,所以GAS并 不知道我们已经改变了edx和ebx的值,如果程序的上下文需要edx或ebx作暂存,这样就会引起严重的后果.对于变量_booga也存在一样的问题. 为了解决这个问题,就要用到扩展的行内汇编语法.

    三 扩展的行内汇编

    扩展的行内汇编类似于Watcom.

    基本的格式是:
    asm ( "statements" : output_regs : input_regs : clobbered_regs);
    clobbered_regs指的是被改变的寄存器.

    下面是一个例子(为方便起见,我使用全局变量):

    int count=1;
    int value=1;
    int buf[10];
    void main()
    {
    asm(
    "cld \n\t"
    "rep \n\t"
    "stosl"
    :
    : "c" (count), "a" (value) , "D" (buf[0])
    : "%ecx","%edi" );
    }

    得到的主要汇编代码为:

    movl count,%ecx
    movl value,%eax
    movl buf,%edi
    #APP
    cld
    rep
    stosl
    #NO_APP

    cld,rep,stos就不用多解释了.这几条语句的功能是向buf中写上count个value值.冒号后的语句指明输入,输出和被改变的寄存器.通 过冒号以后的语句,编译器就知道你的指令需要和改变哪些寄存器,从而可以优化寄存器的分配.
    其中符号"c"(count)指示要把count的值放入ecx寄存器

    类似的还有:

    a eax
    b ebx
    c ecx
    d edx
    S esi
    D edi
    I 常数值,(0 - 31)
    q,r 动态分配的寄存器
    g eax,ebx,ecx,edx或内存变量
    A 把eax和edx合成一个64位的寄存器(use long longs)

    我们也可以让gcc自己选择合适的寄存器.
    如下面的例子:
    asm("leal (%1,%1,4),%0"
    : "=r" (x)
    : "0" (x) );

    这段代码实现5*x的快速乘法.
    得到的主要汇编代码为:
    movl x,%eax
    #APP
    leal (%eax,%eax,4),%eax
    #NO_APP
    movl %eax,x

    几点说明:

    1.使用q指示编译器从eax,ebx,ecx,edx分配寄存器.使用r指示编译器从eax,ebx,ecx,edx,esi,edi分配寄存器.

    2.我们不必把编译器分配的寄存器放入改变的寄存器列表,因为寄存器已经记住了它们.

    3."="是标示输出寄存器,必须这样用.

    4.数字%n的用法:

    数字表示的寄存器是按照出现和从左到右的顺序映射到用"r"或"q"请求的寄存器.如果我们要重用"r"或"q"请求的寄存器的话,就可以使用它们.

    5.如果强制使用固定的寄存器的话,如不用%1,而用ebx,则asm("leal (%%ebx,%%ebx,4),%0"

    : "=r" (x)
    : "0" (x) );

    注意要使用两个%,因为一个%的语法已经被%n用掉了.

    下面可以来解释letter 4854-4855的问题:


    1、变量加下划线和双下划线有什么特殊含义吗?
    加下划线是指全局变量,但我的gcc中加不加都无所谓.

    2、以上定义用如下调用时展开会是什么意思?
    #define _syscall1(type,name,type1,arg1) \
    type name(type1 arg1) \
    { \
    long __res; \
    /* __res应该是一个全局变量 */
    __asm__ volatile ("int $0x80" \
    /* volatile 的意思是不允许优化,使编译器严格按照你的汇编代码汇编*/
    : "=a" (__res) \
    /* 产生代码 movl %eax, __res */
    : "0" (__NR_##name),"b" ((long)(arg1))); \
    /* 如果我没记错的话,这里##指的是两次宏展开.
      即用实际的系统调用名字代替"name",然后再把__NR_...展开.
      接着把展开的常数放入eax,把arg1放入ebx */
    if (__res >= 0) \
    return (type) __res; \
    errno = -__res; \
    return -1; \
    }

    [目录]


    x86内联汇编

    本文提供了在 Linux 平台上使用和构造 x86 内联汇编的概括性介绍。他介绍了内联汇编及其各种用法的基础知识,提供了一些基本的内联汇编编码指导,并解释了在 Linux 内核中内联汇编代码的一些实例。
    如果您是 Linux 内核的开发人员,您会发现自己经常要对与体系结构高度相关的功能进行编码或优化代码路径。您很可能是通过将汇编语言指令插入到 C 语句的中间(又称为内联汇编的一种方法)来执行这些任务的。让我们看一下 Linux 中内联汇编的特定用法。(我们将讨论限制在 IA32 汇编。)
    [目录]


    简述

    GNU 汇编程序简述
    让我们首先看一下 Linux 中使用的基本汇编程序语法。GCC(用于 Linux 的 GNU C 编译器)使用 AT&T 汇编语法。下面列出了这种语法的一些基本规则。(该列表肯定不完整;只包括了与内联汇编相关的那些规则。)

    寄存器命名
    寄存器名称有 % 前缀。即,如果必须使用 eax,它应该用作 %eax。

    源操作数和目的操作数的顺序
    在所有指令中,先是源操作数,然后才是目的操作数。这与将源操作数放在目的操作数之后的 Intel 语法不同。
    mov %eax, %ebx, transfers the contents of eax to ebx.

    操作数大小
    根据操作数是字节 (byte)、字 (word) 还是长型 (long),指令的后缀可以是 b、w 或 l。这并不是强制性的;GCC 会尝试通过读取操作数来提供相应的后缀。但手工指定后缀可以改善代码的可读性,并可以消除编译器猜测不正确的可能性。

    movb %al, %bl -- Byte move
    movw %ax, %bx -- Word move
    movl %eax, %ebx -- Longword move

    立即操作数
    通过使用 $ 指定直接操作数。

    movl $0xffff, %eax -- will move the value of 0xffff into eax register.

    间接内存引用
    任何对内存的间接引用都是通过使用 ( ) 来完成的。

    movb (%esi), %al -- will transfer the byte in the memory
    pointed by esi into al register

    [目录]


    内联汇编

    内联汇编
    GCC 为内联汇编提供特殊结构,它具有以下格式:
    asm ( assembler template
    : output operands (optional)
    : input operands (optional)
    : list of clobbered registers (optional)
    );

    本例中,汇编程序模板由汇编指令组成。输入操作数是充当指令输入操作数使用的 C 表达式。输出操作数是将对其执行汇编指令输出的 C 表达式。

    内联汇编的重要性体现在它能够灵活操作,而且可以使其输出通过 C 变量显示出来。因为它具有这种能力,所以 "asm" 可以用作汇编指令和包含它的 C 程序之间的接口。

    一个非常基本但很重要的区别在于简单内联汇编只包括指令,而扩展内联汇编包括操作数。要说明这一点,考虑以下示例:

    内联汇编的基本要素

    {
    int a=10, b;
    asm ("movl %1, %%eax;
    movl %%eax, %0;"
    :"=r"(b) /* output */
    :"r"(a) /* input */
    :"%eax"); /* clobbered register */
    }

    在上例中,我们使用汇编指令使 "b" 的值等于 "a"。请注意以下几点:
    "b" 是输出操作数,由 %0 引用,"a" 是输入操作数,由 %1 引用。
    "r" 是操作数的约束,它指定将变量 "a" 和 "b" 存储在寄存器中。请注意,输出操作数约束应该带有一个约束修饰符 "=",指定它是输出操作数。
    要在 "asm" 内使用寄存器 %eax,%eax 的前面应该再加一个 %,换句话说就是 %%eax,因为 "asm" 使用 %0、%1 等来标识变量。任何带有一个 % 的数都看作是输入/输出操作数,而不认为是寄存器。
    第三个冒号后的修饰寄存器 %eax 告诉将在 "asm" 中修改 GCC %eax 的值,这样 GCC 就不使用该寄存器存储任何其它的值。
    movl %1, %%eax 将 "a" 的值移到 %eax 中,movl %%eax, %0 将 %eax 的内容移到 "b" 中。
    因为 "b" 被指定成输出操作数,因此当 "asm" 的执行完成后,它将反映出更新的值。换句话说,对 "asm" 内 "b" 所做的更改将在 "asm" 外反映出来。


    [目录]


    程序模板

    汇编程序模板是一组插入到 C 程序中的汇编指令(可以是单个指令,也可以是一组指令)。每条指令都应该由双引号括起,或者整组指令应该由双引号括起。每条指令还应该用一个定界符结尾。 有效的定界符为新行 (\n) 和分号 (;)。 '\n' 后可以跟一个 tab(\t) 作为格式化符号,增加 GCC 在汇编文件中生成的指令的可读性。 指令通过数 %0、%1 等来引用 C 表达式(指定为操作数)。

    如果希望确保编译器不会在 "asm" 内部优化指令,可以在 "asm" 后使用关键字 "volatile"。如果程序必须与 ANSI C 兼容,则应该使用 __asm__ 和 __volatile__,而不是 asm 和 volatile。

    [目录]


    操作数

    C 表达式用作 "asm" 内的汇编指令操作数。在汇编指令通过对 C 程序的 C 表达式进行操作来执行有意义的作业的情况下,操作数是内联汇编的主要特性。

    每个操作数都由操作数约束字符串指定,后面跟用括弧括起的 C 表达式,例如:"constraint" (C expression)。操作数约束的主要功能是确定操作数的寻址方式。

    可以在输入和输出部分中同时使用多个操作数。每个操作数由逗号分隔开。

    在汇编程序模板内部,操作数由数字引用。如果总共有 n 个操作数(包括输入和输出),那么第一个输出操作数的编号为 0,逐项递增,最后那个输入操作数的编号为 n-1。总操作数的数目限制在 10,如果机器描述中任何指令模式中的最大操作数数目大于 10,则使用后者作为限制。

    [目录]


    修饰寄存器列表

    如果 "asm" 中的指令指的是硬件寄存器,可以告诉 GCC 我们将自己使用和修改它们。这样,GCC 就不会假设它装入到这些寄存器中的值是有效值。通常不需要将输入和输出寄存器列为 clobbered,因为 GCC 知道 "asm" 使用它们(因为它们被明确指定为约束)。不过,如果指令使用任何其它的寄存器,无论是明确的还是隐含的(寄存器不在输入约束列表中出现,也不在输出约束列 表中出现),寄存器都必须被指定为修饰列表。修饰寄存器列在第三个冒号之后,其名称被指定为字符串。

    至于关键字,如果指令以某些不可预知且不明确的方式修改了内存,则可能将 "memory" 关键字添加到修饰寄存器列表中。这样就告诉 GCC 不要在不同指令之间将内存值高速缓存在寄存器中。

    [目录]


    操作数约束

    前面提到过,"asm" 中的每个操作数都应该由操作数约束字符串描述,后面跟用括弧括起的 C 表达式。操作数约束主要是确定指令中操作数的寻址方式。约束也可以指定:

    ·是否允许操作数位于寄存器中,以及它可以包括在哪些种类的寄存器中
    ·操作数是否可以是内存引用,以及在这种情况下使用哪些种类的地址
    ·操作数是否可以是立即数

    约束还要求两个操作数匹配。

    常用约束

    在可用的操作数约束中,只有一小部分是常用的;下面列出了这些约束以及简要描述。有关操作数约束的完整列表,请参考 GCC 和 GAS 手册。

    寄存器操作数约束 (r)
    使用这种约束指定操作数时,它们存储在通用寄存器中。请看下例:
    asm ("movl %%cr3, %0\n" :"=r"(cr3val));
    这里,变量 cr3val 保存在寄存器中,%cr3 的值复制到寄存器上,cr3val 的值从该寄存器更新到内存中。指定 "r" 约束时,GCC 可以将变量 cr3val 保存在任何可用的 GPR 中。要指定寄存器,必须通过使用特定的寄存器约束直接指定寄存器名。

    a %eax
    b %ebx
    c %ecx
    d %edx
    S %esi
    D %edi

    内存操作数约束 (m)
    当操作数位于内存中时,任何对它们执行的操作都将在内存位置中直接发生,这与寄存器约束正好相反,后者先将值存储在要修改的寄存器中,然后将它写回内存位 置中。但寄存器约束通常只在对于指令来说它们是绝对必需的,或者它们可以大大提高进程速度时使用。当需要在 "asm" 内部更新 C 变量,而您又确实不希望使用寄存器来保存其值时,使用内存约束最为有效。例如,idtr 的值存储在内存位置 loc 中:
    ("sidt %0\n" : :"m"(loc));

    匹配(数字)约束
    在某些情况下,一个变量既要充当输入操作数,也要充当输出操作数。可以通过使用匹配约束在 "asm" 中指定这种情况。

    asm ("incl %0" :"=a"(var):"0"(var));

    在匹配约束的示例中,寄存器 %eax 既用作输入变量,也用作输出变量。将 var 输入读取到 %eax,增加后将更新的 %eax 再次存储在 var 中。这里的 "0" 指定第 0 个输出变量相同的约束。即,它指定 var 的输出实例只应该存储在 %eax 中。该约束可以用于以下情况:

    ·输入从变量中读取,或者变量被修改后,修改写回到同一变量中
    ·不需要将输入操作数和输出操作数的实例分开
    ·使用匹配约束最重要的意义在于它们可以导致有效地使用可用寄存器。


    [目录]


    示例

    一般内联汇编用法示例
    以下示例通过各种不同的操作数约束说明了用法。有如此多的约束以至于无法将它们一一列出,这里只列出了最经常使用的那些约束类型。


    [目录]


    寄存器约束

    "asm" 和寄存器约束 "r"
    让我们先看一下使用寄存器约束 r 的 "asm"。我们的示例显示了 GCC 如何分配寄存器,以及它如何更新输出变量的值。

    int main(void)
    {
    int x = 10, y;
    asm ("movl %1, %%eax;
    "movl %%eax, %0;"
    :"=r"(y) /* y is output operand */
    :"r"(x) /* x is input operand */
    :"%eax"); /* %eax is clobbered register */
    }

    在该例中,x 的值复制为 "asm" 中的 y。x 和 y 都通过存储在寄存器中传递给 "asm"。为该例生成的汇编代码如下:

    main:
    pushl %ebp
    movl %esp,%ebp
    subl $8,%esp
    movl $10,-4(%ebp)
    movl -4(%ebp),%edx /* x=10 is stored in %edx */
    #APP /* asm starts here */
    movl %edx, %eax /* x is moved to %eax */
    movl %eax, %edx /* y is allocated in edx and updated */
    #NO_APP /* asm ends here */
    movl %edx,-8(%ebp) /* value of y in stack is updated with the value in %edx */

    当使用 "r" 约束时,GCC 在这里可以自由分配任何寄存器。在我们的示例中,它选择 %edx 来存储 x。在读取了 %edx 中 x 的值后,它为 y 也分配了相同的寄存器。

    因为 y 是在输出操作数部分中指定的,所以 %edx 中更新的值存储在 -8(%ebp),堆栈上 y 的位置中。如果 y 是在输入部分中指定的,那么即使它在 y 的临时寄存器存储值 (%edx) 中被更新,堆栈上 y 的值也不会更新。

    因为 %eax 是在修饰列表中指定的,GCC 不在任何其它地方使用它来存储数据。

    输入 x 和输出 y 都分配在同一个 %edx 寄存器中,假设输入在输出产生之前被消耗。请注意,如果您有许多指令,就不是这种情况了。要确保输入和输出分配到不同的寄存器中,可以指定 & 约束修饰符。下面是添加了约束修饰符的示例。

    int main(void)
    {
    int x = 10, y;
    asm ("movl %1, %%eax;
    "movl %%eax, %0;"
    :"=&r"(y) /* y is output operand, note the & constraint modifier. */
    :"r"(x) /* x is input operand */
    :"%eax"); /* %eax is clobbered register */
    }

    以下是为该示例生成的汇编代码,从中可以明显地看出 x 和 y 存储在 "asm" 中不同的寄存器中。

    main:
    pushl %ebp
    movl %esp,%ebp
    subl $8,%esp
    movl $10,-4(%ebp)
    movl -4(%ebp),%ecx /* x, the input is in %ecx */
    #APP
    movl %ecx, %eax
    movl %eax, %edx /* y, the output is in %edx */
    #NO_APP
    movl %edx,-8(%ebp)

    特定寄存器约束的使用
    现在让我们看一下如何将个别寄存器作为操作数的约束指定。在下面的示例中,cpuid 指令采用 %eax 寄存器中的输入,然后在四个寄存器中给出输出:%eax、%ebx、%ecx、%edx。对 cpuid 的输入(变量 "op")传递到 "asm" 的 eax 寄存器中,因为 cpuid 希望它这样做。在输出中使用 a、b、c 和 d 约束,分别收集四个寄存器中的值。

    asm ("cpuid"
    : "=a" (_eax),
    "=b" (_ebx),
    "=c" (_ecx),
    "=d" (_edx)
    : "a" (op));

    在下面可以看到为它生成的汇编代码(假设 _eax、_ebx 等... 变量都存储在堆栈上):

    movl -20(%ebp),%eax /* store 'op' in %eax -- input */
    #APP
    cpuid
    #NO_APP
    movl %eax,-4(%ebp) /* store %eax in _eax -- output */
    movl %ebx,-8(%ebp) /* store other registers in
    movl %ecx,-12(%ebp)
    respective output variables */
    movl %edx,-16(%ebp)

    strcpy 函数可以通过以下方式使用 "S" 和 "D" 约束来实现:

    asm ("cld\n
    rep\n
    movsb"
    : /* no input */
    :"S"(src), "D"(dst), "c"(count));

    通过使用 "S" 约束将源指针 src 放入 %esi 中,使用 "D" 约束将目的指针 dst 放入 %edi 中。因为 rep 前缀需要 count 值,所以将它放入 %ecx 中。

    在下面可以看到另一个约束,它使用两个寄存器 %eax 和 %edx 将两个 32 位的值合并在一起,然后生成一个64 位的值:

    #define rdtscll(val) \
    __asm__ __volatile__ ("rdtsc" : "=A" (val))
    The generated assembly looks like this (if val has a 64 bit memory space).
    #APP
    rdtsc
    #NO_APP
    movl %eax,-8(%ebp) /* As a result of A constraint
    movl %edx,-4(%ebp)
    %eax and %edx serve as outputs */
    Note here that the values in %edx:%eax serve as 64 bit output.


    [目录]


    匹配约束

    使用匹配约束
    在下面将看到系统调用的代码,它有四个参数:

    #define _syscall4(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4) \
    type name (type1 arg1, type2 arg2, type3 arg3, type4 arg4) \
    { \
    long __res; \
    __asm__ volatile ("int $0x80" \
    : "=a" (__res) \
    : "0" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2)), \
    "d" ((long)(arg3)),"S" ((long)(arg4))); \
    __syscall_return(type,__res); \
    }

    在上例中,通过使用 b、c、d 和 S 约束将系统调用的四个自变量放入 %ebx、%ecx、%edx 和 %esi 中。请注意,在输出中使用了 "=a" 约束,这样,位于 %eax 中的系统调用的返回值就被放入变量 __res 中。通过将匹配约束 "0" 用作输入部分中第一个操作数约束,syscall 号 __NR_##name 被放入 %eax 中,并用作对系统调用的输入。这样,这里的 %eax 既可以用作输入寄存器,又可以用作输出寄存器。没有其它寄存器用于这个目的。另请注意,输入(syscall 号)在产生输出(syscall 的返回值)之前被消耗(使用)。

    [目录]


    内存操作数约束

    内存操作数约束的使用
    请考虑下面的原子递减操作:

    __asm__ __volatile__(
    "lock; decl %0"
    :"=m" (counter)
    :"m" (counter));

    为它生成的汇编类似于:

    #APP
    lock
    decl -24(%ebp) /* counter is modified on its memory location */
    #NO_APP.

    您可能考虑在这里为 counter 使用寄存器约束。如果这样做,counter 的值必须先复制到寄存器,递减,然后对其内存更新。但这样您会无法理解锁定和原子性的全部意图,这些明确显示了使用内存约束的必要性。

    [目录]


    修饰寄存器

    使用修饰寄存器
    请考虑内存拷贝的基本实现。

    asm ("movl $count, %%ecx;
    up: lodsl;
    stosl;
    loop up;"
    : /* no output */
    :"S"(src), "D"(dst) /* input */
    :"%ecx", "%eax" ); /* clobbered list */

    当 lodsl 修改 %eax 时,lodsl 和 stosl 指令隐含地使用它。%ecx 寄存器明确装入 count。但 GCC 在我们通知它以前是不知道这些的,我们是通过将 %eax 和 %ecx 包括在修饰寄存器集中来通知 GCC 的。在完成这一步之前,GCC 假设 %eax 和 %ecx 是自由的,它可能决定将它们用作存储其它的数据。请注意,%esi 和 %edi 由 "asm" 使用,它们不在修饰列表中。这是因为已经声明 "asm" 将在输入操作数列表中使用它们。这里最低限度是,如果在 "asm" 内部使用寄存器(无论是明确还是隐含地),既不出现在输入操作数列表中,也不出现在输出操作数列表中,必须将它列为修饰寄存器。

    [目录]


    不同的CPU下最佳编译参数

    gcc在不同的体系机构/CPU下编译效果有不同,需要使用不同的编译参数达到最佳效果。


    一、1.2版(gcc 2.9.x版)

    -pipe -fomit-frame-pointer"
    CXXFLAGS="-march=i486 -O3 -pipe -fomit-frame-pointer"

    Pentium, Pentium MMX+, Celeron (Mendocino) (Intel)
    CHOST="i586-pc-linux-gnu"
    CFLAGS="-march=pentium -O3 -pipe -fomit-frame-pointer"
    CXXFLAGS="-march=pentium -O3 -pipe -fomit-frame-pointer"

    Pentium Pro/II/III/4, Celeron (Coppermine), Celeron (Willamette?) (Intel)
    CHOST="i686-pc-linux-gnu"
    CFLAGS="-march=i686 -O3 -pipe -fomit-frame-pointer"
    CXXFLAGS="-march=i686 -O3 -pipe -fomit-frame-pointer"

    Eden C3/Ezra (Via)
    CHOST="i586-pc-linux-gnu"
    CFLAGS="-march=i586 -O3 -pipe -fomit-frame-pointer"
    CXXFLAGS="-march=i586 -O3 -pipe -fomit-frame-pointer"

    Quote : I did the original gentoo install using 1.2, with gcc 2.95 using -march=i586. i686 won't work.

    K6 or beyond (AMD)
    CHOST="i586-pc-linux-gnu"
    CFLAGS="-march=k6 -O3 -pipe -fomit-frame-pointer"
    CXXFLAGS="-march=k6 -O3 -pipe -fomit-frame-pointer"

    (A Duron will report "Athlon" in its /proc/cpuinfo)

    Athlon (AMD)
    CHOST="i686-pc-linux-gnu"
    CFLAGS="-march=k6 -O3 -pipe -fomit-frame-pointer"
    CXXFLAGS="-march=k6 -O3 -pipe -fomit-frame-pointer"

    For the following, i don't know of any flag that enhance performances..., do you ?

    PowerPC
    CHOST="powerpc-unknown-linux-gnu"
    CFLAGS="-O3 -pipe -fomit-frame-pointer"
    CXXFLAGS="-O3 -pipe -fomit-frame-pointer"

    Sparc
    CHOST="sparc-unknown-linux-gnu"
    CFLAGS="-O3 -pipe -fomit-frame-pointer"
    CXXFLAGS="-O3 -pipe -fomit-frame-pointer"

    Sparc 64
    CHOST="sparc64-unknown-linux-gnu"
    CFLAGS="-O3 -pipe -fomit-frame-pointer"
    CXXFLAGS="-O3 -pipe -fomit-frame-pointer"

    二、1.4版(gcc 3.x版):


    i386 (Intel), do you really want to install gentoo on that ?
    CHOST="i386-pc-linux-gnu"
    CFLAGS="-march=i386 -O3 -pipe -fomit-frame-pointer"
    CXXFLAGS="-march=i386 -O3 -pipe -fomit-frame-pointer"

    i486 (Intel), do you really want to install gentoo on that ?
    CHOST="i486-pc-linux-gnu"
    CFLAGS="-march=i486 -O3 -pipe -fomit-frame-pointer"
    CXXFLAGS="-march=i486 -O3 -pipe -fomit-frame-pointer"

    Pentium 1 (Intel)
    CHOST="i586-pc-linux-gnu"
    CFLAGS="-march=pentium -O3 -pipe -fomit-frame-pointer"
    CXXFLAGS="-march=pentium -O3 -pipe -fomit-frame-pointer"

    Pentium MMX (Intel)
    CHOST="i586-pc-linux-gnu"
    CFLAGS="-march=pentium-mmx -O3 -pipe -fomit-frame-pointer"
    CXXFLAGS="-march=pentium-mmx -O3 -pipe -fomit-frame-pointer"

    Pentium PRO (Intel)
    CHOST="i686-pc-linux-gnu"
    CFLAGS="-march=pentiumpro -O3 -pipe -fomit-frame-pointer"
    CXXFLAGS="-march=pentiumpro -O3 -pipe -fomit-frame-pointer"

    Pentium II (Intel)
    CHOST="i686-pc-linux-gnu"
    CFLAGS="-march=pentium2 -O3 -pipe -fomit-frame-pointer"
    CXXFLAGS="-march=pentium2 -O3 -pipe -fomit-frame-pointer"

    Celeron (Mendocino), aka Celeron1 (Intel)
    CHOST="i686-pc-linux-gnu"
    CFLAGS="-march=pentium2 -O3 -pipe -fomit-frame-pointer"
    CXXFLAGS="-march=pentium2 -O3 -pipe -fomit-frame-pointer"

    Pentium III (Intel)
    CHOST="i686-pc-linux-gnu"
    CFLAGS="-march=pentium3 -O3 -pipe -fomit-frame-pointer"
    CXXFLAGS="-march=pentium3 -O3 -pipe -fomit-frame-pointer"

    Celeron (Coppermine) aka Celeron2 (Intel)
    CHOST="i686-pc-linux-gnu"
    CFLAGS="-march=pentium3 -O3 -pipe -fomit-frame-pointer"
    CXXFLAGS="-march=pentium3 -O3 -pipe -fomit-frame-pointer"

    Celeron (Willamette?) (Intel)
    CHOST="i686-pc-linux-gnu"
    CFLAGS="-march=pentium4 -O3 -pipe -fomit-frame-pointer"
    CXXFLAGS="-march=pentium4 -O3 -pipe -fomit-frame-pointer"

    Pentium 4 (Intel)
    CHOST="i686-pc-linux-gnu"
    CFLAGS="-march=pentium4 -O3 -pipe -fomit-frame-pointer"
    CXXFLAGS="-march=pentium4 -O3 -pipe -fomit-frame-pointer"

    Eden C3/Ezra (Via)
    CHOST="i586-pc-linux-gnu"
    CFLAGS="-march=i586 -m3dnow -O3 -pipe -fomit-frame-pointer"
    CXXFLAGS="-march=i586 -m3dnow -O3 -pipe -fomit-frame-pointer"

    quote : the ezra doesn't have any special instructions that you could optimize for, just consider is a K6-3...basically
    a p2 with 3dnow

    K6 (AMD)
    CHOST="i586-pc-linux-gnu"
    CFLAGS="-march=k6 -O3 -pipe -fomit-frame-pointer"
    CXXFLAGS="-march=k6 -O3 -pipe -fomit-frame-pointer"

    K6-2 (AMD)
    CHOST="i586-pc-linux-gnu"
    CFLAGS="-march=k6-2 -O3 -pipe -fomit-frame-pointer"
    CXXFLAGS="-march=k6-2 -O3 -pipe -fomit-frame-pointer"

    K6-3 (AMD)
    CHOST="i586-pc-linux-gnu"
    CFLAGS="-march=k6-3 -O3 -pipe -fomit-frame-pointer"
    CXXFLAGS="-march=k6-3 -O3 -pipe -fomit-frame-pointer"

    Athlon (AMD)
    CHOST="i686-pc-linux-gnu"
    CFLAGS="-march=athlon -O3 -pipe -fomit-frame-pointer"
    CXXFLAGS="-march=athlon -O3 -pipe -fomit-frame-pointer"

    Athlon-tbird, aka K7 (AMD)
    CHOST="i686-pc-linux-gnu"
    CFLAGS="-march=athlon-tbird -O3 -pipe -fomit-frame-pointer"
    CXXFLAGS="-march=athlon-tbird -O3 -pipe -fomit-frame-pointer"

    Athlon-tbird XP (AMD)
    CHOST="i686-pc-linux-gnu"
    CFLAGS="-march=athlon-xp -O3 -pipe -fomit-frame-pointer"
    CXXFLAGS="-march=athlon-xp -O3 -pipe -fomit-frame-pointer"

    Athlon 4(AMD)
    CHOST="i686-pc-linux-gnu"
    CFLAGS="-march=athlon-4 -O3 -pipe -fomit-frame-pointer"
    CXXFLAGS="-march=athlon-4 -O3 -pipe -fomit-frame-pointer"

    Athlon XP (AMD)
    CHOST="i686-pc-linux-gnu"
    CFLAGS="-march=athlon-xp -O3 -pipe -fomit-frame-pointer"
    CXXFLAGS="-march=athlon-xp -O3 -pipe -fomit-frame-pointer"

    Athlon MP (AMD)
    CHOST="i686-pc-linux-gnu"
    CFLAGS="-march=athlon-mp -O3 -pipe -fomit-frame-pointer"
    CXXFLAGS="-march=athlon-mp -O3 -pipe -fomit-frame-pointer"

    603 (PowerPC)
    CHOST="powerpc-unknown-linux-gnu"
    CFLAGS="-O3 -pipe -fomit-frame-pointer -fsigned-char"
    CXXFLAGS="-O3 -pipe -fomit-frame-pointer -fsigned-char"

    603e (PowerPC)
    CHOST="powerpc-unknown-linux-gnu"
    CFLAGS="-O3 -pipe -fomit-frame-pointer -fsigned-char"
    CXXFLAGS="-O3 -pipe -fomit-frame-pointer -fsigned-char"

    604 (PowerPC)
    CHOST="powerpc-unknown-linux-gnu"
    CFLAGS="-O3 -pipe -fomit-frame-pointer -fsigned-char"
    CXXFLAGS="-O3 -pipe -fomit-frame-pointer -fsigned-char"

    604e (PowerPC)
    CHOST="powerpc-unknown-linux-gnu"
    CFLAGS="-O3 -pipe -fomit-frame-pointer -fsigned-char"
    CXXFLAGS="-O3 -pipe -fomit-frame-pointer -fsigned-char"

    750 aka as G3 (PowerPC)
    CHOST="powerpc-unknown-linux-gnu"
    CFLAGS="-mcpu=750 -O3 -pipe -fomit-frame-pointer
    -fsigned-char"
    CXXFLAGS="-mcpu=750 -O3 -pipe -fomit-frame-pointer
    -fsigned-char"

    Note: do not use -march=

    7400, aka G4 (PowerPC)
    CHOST="powerpc-unknown-linux-gnu"
    CFLAGS="-mcpu=7400 -O3 -pipe -fomit-frame-pointer
    -fsigned-char -maltivec"
    CXXFLAGS="-mcpu=7400 -O3 -pipe -fomit-frame-pointer
    -fsigned-char -maltivec"

    Note: do not use -march=

    7450, aka G4 second generation (PowerPC)
    CHOST="powerpc-unknown-linux-gnu"
    CFLAGS="-mcpu=7450 -O3 -pipe -fomit-frame-pointer
    -fsigned-char -maltivec"
    CXXFLAGS="-mcpu=7450 -O3 -pipe -fomit-frame-pointer
    -fsigned-char -maltivec"

    Note: do not use -march=

    PowerPC (If you don't know which one)
    CHOST="powerpc-unknown-linux-gnu"
    CFLAGS="-O3 -pipe -fomit-frame-pointer -fsigned-char"
    CXXFLAGS="-O3 -pipe -fomit-frame-pointer -fsigned-char"

    Sparc
    CHOST="sparc-unknown-linux-gnu"
    CFLAGS="-O3 -pipe -fomit-frame-pointer"
    CXXFLAGS="-O3 -pipe -fomit-frame-pointer"

    Sparc 64
    CHOST="sparc64-unknown-linux-gnu"
    CFLAGS="-O3 -pipe -fomit-frame-pointer"
    CXXFLAGS="-O3 -pipe -fomit-frame-pointer"

    [目录]


    代码维护

    [目录]


    简单cvs

    简单CVS
    一、所要解决的问题
    由于软件项目越来越大,也增加了软件项目管理的难度。在开发组中,每个成员都要保留一个副本,在开发中非常容易引起冲突。CVS 就是为了解决这个问题的。

    a、修改同步,防止一名开发人员的修改覆盖其他人的成果。(check out、read only)

    b、维护不同的版本。(按 version 查找)

    c、可查找历史记录。防止 bug 的再引入。(diff)

    CVS 为了解决这个问题,采用的方式是:

    当开发人员对源代码进行修改时,修改的内容被登记(check in)到了 CVS 仓库(repository)中。仓库中保存了代码的主控副本,以及历次修改的历史信息。它不保存文件的每个版本,而只是简单的记录发生在每个版本间的不 同,节省磁盘空间。它能做到:

    a、使开发人员的目录和仓库保持一致。可以把自己的修改提交(commit)给仓库,让仓库更新自己。

    b、允许代码派生。可以进行测试,如果失败,可以消除所做的修改,维持原结果。

    c、检索任何一个版本。

    二、使用 CVS:

    a、建立仓库:设置并 export CVSROOT 变量,并设置仓库目录。比如创建 /home/cvsroot 目录,并合理设置权限,在 .bash_profile 中加入:export CVSROOT=$HOME/cvsroot;运行 cvs init;设置让用户输入日志信息的默认的编辑器:export EDITOR=vim。如果使用的是网络,则 CVSROOT 变量的形式是: export CVSROOT=:exit:user@server:/path,比如:export CVSROOT=:exit:david@power/home/projects/repository

    b、导入文件或目录:cvs import filename_or_directory vender_tag release_tag;这三个参数的含义是:在仓库中这些导入的文件所在的目录、供应商标记、发行标记。比如 cvs import step1 david start。导入时的 N 表示所导入的文件都是新文件。

    c、使用时导出文件,用:cvs checkout directory_name;参数含义是仓库中所在的目录。建立新的目录,而不是获取他人的改动

    d、对文件修改后,保存修改到仓库:cvs commit。

    e、如果要获得他人的修改,使用 cvs update,U 表示本地的一个文件已经被更新。如果已经对文件进行了修改,而此是他人已经修改了该文件并提交,cvs 将告诉用户发生冲突和冲突的位置。

    f、添加一个文件:cvs add filename; cvs commit。

    g、删除一个文件:先在本地删除,然后使用 cvs remove file_name; cvs commit。

    三、使用标记

    可以使用标记记录某个时刻文件的内容,这在制作发行版本的过程中非常有用:

    cvs tag release1.0

    改动后发现不正常,重新获得这个版本,则使用:cvs checkout -r release1.0

    四、测试性代码:

    当其中一个开发者对代码进行改进,但未来结果不能确定时,可以使用 cvs 产生出一个代码的分之,这并不改变主控代码:cvs tag -b for_test。导出该代码的命令是:cvs checkout -r for_test example。由于在已有的目录树中不会使用该分之,因此必须重新建立目录树。如果测试成功,则要求将主控代码和测试代码合并,则先导出主控代码,然后 合并:

    cvs checkout;cvs update -jfor_test;cvs commit。


    [目录]


    automake

    通常情况下,在写完自己代码后,使用 make 命令进行编译。make 命令其实什么也不做,知识读取一个叫 Makefile 的文件,从中获得 dependence and rule,然后调用 gcc 进行编译。但是 Makefile 比较复杂,变化技巧也比较多。对于一个大的工程项目来说,如果没有一个统一的风格,在工程延续的时候改动 Makefile 会很麻烦,也容易出错误。所以这时就有了使用 automake 的需求。使用 automake,只需要掌握一点点规则,定义一些变量,就能自动生成 Makefile。而这些 Makefile 有着统一的格式和结构,便于修改。下面就如何使用 automake 举出一个实际的例子。

    2.1 使用 automake 的前提条件


    在使用 automake 前,请先确认在系统中安装了如下软件:

    GNU Automake

    GNU Autoconf

    GNU m4

    perl

    GNU Libtool(如果需要产生 shared library)如果没有的话,请在发行版中找相应的 rpm 包。

    2.2 制作 configure 脚本

    autoconf 是用来生成自动配置软件源代码脚本(configure)的工具。configure 脚本独立于 autoconf 运行,而且在运行的过程中,不需要用户的干预,通常不需要附带参数。它是用来检验软件必须的参数的。autoconf 从一个列举编译软件时所需要各种参数的模板文件中创建 configure。autoconf 需要 GNU m4 来生成该脚本。

    由 autoconf 生成的脚本一般起名为 configure。当运行时,configure 创建了多个文件,并对这些文件中的配置参数赋予适当的值。由 configure 创建生成的文件有:

    1。一个或多个 Makefile,在软件源代码的每个目录中都生成一个 Makefile。

    2。还可选的生成 C 头文件——configurable,包含了各种 #define 声明。

    3。一个名为 config.status 的脚本,当运行时,重新生成上面的文件。

    4。一个名为 config.cache 的脚本,保存运行检测的结果。

    5。一个名为 config.log 的文件,保存有编译器生成的信息,用于调试 configure。

    为了让 autoconf 生成 configure 脚本,需要以 configure.in 为参数调用 autoconf。如果要检测自己的各种参数,以作为对 autoconf 的补充,则需要写 aclocal.m4 和 acsite.m4 的文件。如果要使用 C 头文件,需要写 acconfig.h,并且将 autoconf 生成的 config.h.in 同软件一起发行。

    your source files --> [autoscan*] --> [configure.scan] --> configure.in


    configure.in --. .------> autoconf* -----> configure

    +---+

    [aclocal.m4] --+ `---.

    [acsite.m4] ---' |

    +--> [autoheader*] -> [config.h.in]

    [acconfig.h] ----. |

    +-----'

    [config.h.top] --+

    [config.h.bot] --'


    Makefile.in -------------------------------> Makefile.in

    Files used in configuring a software package:

    .-------------> config.cache

    configure* ------------+-------------> config.log

    |

    [config.h.in] -. v .-> [config.h] -.

    +--> config.status* -+ +--> make*

    Makefile.in ---' `-> Makefile ---'


    编辑 configure.in 文件:

    configure.in 文件中包含了对 autoconf 宏的调用,这些宏是用来检测软件所必须的各项参数的。为了能够得到 configure.in 文件,需要使用 autoscan。configure.in 文件中,在进行各项检测前,必须在最开始调用 AC_INIT,在最后调用 AC_OUTPUT。另外有些宏由于检测的关系是和在文件中的位置相关的。最好每一个宏占用一行。


    使用 autoscan 创建 configure.in 文件

    可以将目录做为参数调用 autoscan,如果不使用参数的化,则认为是当前目录。autoscan 将检查指定目录中的源文件,并创建 configure.scan 文件。在将 configure.scan 改名为 configure.in 文件前,需要手工改动它以进行调整。


    使用 autoconf 创建 configure 脚本

    不带任何参数的运行 autoconf。autoconf 将使用 m4 宏处理器和 autoconf 宏,来处理处理 configure.in 中的宏。


    configure.in 中的宏:

    AC_INIT(在源代码中唯一的一个文件):configure 将检查该文件是否存在,并检查包含它的目录是否存在。

    AC_OUTPUT(文件):指定创建的输出文件。在 configure.in 文件中调用一次。文件名间用空格分开。比如:AC_OUTPUT(Makefile:templates/top.mk lib/Makefile:templates/lib.mk)

    在 configure.in 中,有一些被 autoconf 宏预先定义的变量,重要的有如下几个:

    bindir:安装可执行文件的目录。

    includedir:C 头文件目录。

    infodir:info 页安装目录。

    mandir:安装手册页的目录。

    sbindir:为管理员运行该该程序提供的安装路径。

    srcdir:为 Makefile 提供的源代码路径。

    top_srcdir:源代码的最上层目录。

    LIBS:给连接程序的 -l 选项

    LDFLAGS:给连接程序的 stripping(-s)和其他一些选项。

    DEFS:给 C 编译器的 -D 选项。

    CFLAGS:给 C 编译器的 debug 和优化选项。当调用了 AC_PROG_CC 才有效。

    CPPFLAGS:头文件搜索路径(-I)和给 C 预处理器和编译器的其他选项。

    CXXFLAGS:给 C++ 编译器的 debug 和优化选项。当调用了 AC_PROG_CXX 才有效。

    如果在同一个目录下编译多个程序的话,使用 AC_CONFIG_SUBDIRS 宏,它的语法是:

    AC_CONFIG_SUBDIRS(DIR....):

    其他重要的宏:

    AC_PROG_CC:选择 C 编译器。如果在环境中不设置 CC 的话,则检测 gcc。

    AC_PROG_CXX:选择 C++ 编译器。

    参考文献:

    Autoconf.htm
    2.3 使用 automake
    一般操作

    Automake 工作时,读取一个叫'Makefile.am'的文件,并生成一个'Makefile.in'文件。Makefile.am中定义的宏和目标,会指导 automake生成指定的代码。例如,宏'bin_PROGRAMS'将导致编译和连接的目标被生成。

    Makefile.am中包含的目标和定义的宏被拷贝到生成的文件中去,这允许你添加任意代码到生成的Makefile.in文件中去。例如,使一个 Automake发布中包含一个非标准的dvs-dist目标,Automake的维护者用它来从它的源码控制系统制作一个发布。

    请注意,GNU生成的扩展名不被Automake所识别,在一个'Makefile.am'中使用这样一个扩展名会导致错误。

    Automake试图以一种聪明的方式将相邻的目标(或变量定义)注释重组。

    通常,Makefile.am中定义的目标会覆盖任何由automake自动生成的有相似名字的这样的目标。尽管这是种被支持的属性,但最好避免这么做,因为有些时候,生成的规则很严格。

    类似的,Makefile.am中定义的变量会覆盖任何由automake自动生成的变量定义。这一特性经常要比目标定义的覆盖能力更常用。请注意,很多 automake生成的变量只用于内部使用,在将来发布时他们的名字可能会变化。

    当测试一个变量定义时,Automake降递归的测试在定义中引用的变量。例如,如果Automake看到这段snippet程序中的'foo_SOURCES':

    xs = a.c b.c

    foo_SOURCES = c.c $(xs)

    它将使用文件:'a.c','b.c'和'c.c' 作为foo_SOURCES的内容.

    Automake 也允许不被拷贝到输出的注释形式,所有以'##'开头的行将被Automake 完全忽略.


    深度

    Automake 支持三种目录层次:'flat', 'shallow', 'deep'.

    flat: 所有的文件都在一个目录中. 相应的Makefile.am中缺少SUBDIRS宏. termutils 是一个例子.

    deep: 所有的资源都在子目录中,指定曾目录主要包含配置信息.GNU cpio 是一个很好的例子.GNU tar.相应的最顶层Makefile.am中将包含一个SUBDIR宏,但没有其他的宏来定义要创建的对象.

    shallow: 主资源存在于最顶层目录,而不同的部分(典型的,库函数)在子目录中.Automake 就是这样的一个包.


    严格性

    当Automake 被GNU包维护者使用时,它的确努力去适应,但不要试图使用所有的GNU惯例.

    目前,Automake 支持三种严格性标准:

    foreign:Automake 将只检查绝对必须的东西.

    gnu:Automake 将尽可能多的检查以适应GNU标准, 这是默认项.

    gnits:Automake 将进行检查,以适应“尚未成文”的Gnits标准。 他们基于GNU标准,但更详尽。除非您是Gnits标准的制定者。建议您最好避免这个选项,指导该标准正式发布。


    统一命名规范

    Automake变量一般遵循一套统一的命名规范以很容易的决定如何创建和安装程序(和其他派生对象)。给规范还支持configure时动态决定创建规则。

    在make时,一些变量被用于决定那些对象要被创建。写变量叫做primary variables。例如,PROGRAM变量包括一个要被编译和连接的程序列表。

    另一个变量集用于决定被创建变量被安装到哪里。这些变量以相应的主变量命名,但加一个前缀,表示那些标准目录应被用作安装路径。这些标准目录的名称规定在 GNU标准中。Automake用pkglibdir, pkgincludedir 和 pkgdatadir来展开这一列表。他们和没有pkg前缀的版本一样,只不过有‘@PACHAGE@’扩展,PKGLIBDIR被定义为 $(DATADIR)/@PACKAGE@.

    对每一个主变量,有一个EXTRA_前缀的变量。这个变量用于列出所有对象,至于哪些变量被创建,哪些变量不被创建则取决于configure。之所以需 要这个变量,是因为Automake必须静态的指导要创建对象的完整列表以便生成一个‘Makefile。in’文件。

    例如,cpio 在configure时决定创建那些程序。一些程序被安装在bindir,一些被安装在sbindir:

    EXTRA_PROGRAMS = mt rmt

    bin_PROGRAMS = cpoi pax

    sbin_PRGRAMS = @PROGRAMS@

    定义没有前缀的主变量是错误的(如:PROGRAMS)。值得注意的是,“dir”在作为构成变量名时会被忽略。一次,我们写成bin_PROGRAMS 而不是bindir_PROGRAMS.

    不是每一种对象都得以安装在每个目录下。Automake 将标记那些他认为是错误的尝试,他也能够诊断一些明显的目录名拼写错误。

    有时标准目录--被Automake使用的--不过用。特别的,有时为了清晰,将对象安装在一些预定义的子目录,是很有用的。Automake允许你增加 安装目录。如果以一个变量名(如,zar)加上dir的另一个变量(如,zardir)被定义了,那么他也是合法的。

    例如,如果HTML没支持Automake的一下部分,你就可以用他来安装HTML源文件:

    htmldir = $(prefix)/html

    html_DATA = automake.html

    “noinst”前缀专门指定有问题的对象不被安装。

    “check”前缀表示有问题的对象知道make check命令被执行猜被创建。

    可用的主变量是 'PROGRAMS','LIBRARIES','LISP','SCRIPTS','DATA','HEADERS','MANS'和'TEXINFOS' 导出变量是如何命名的

    有时一个Makefile变量名有一些用户支持的文本导出。例如程序名被重写进Makefile宏名称。Automake读取这些文本,所以他不必遵循命 名规则。当生成后引用时名称中的字符除了字母,数字,下划线夺回被转换为下划线。例如,如果你的程序里有sniff-glue,则导出变量名将会是 sniff_glue_SOURCES,而不是sniff-glue_SOURCES.


    一些例子


    一个完整简单的例子

    假设你写了一个名为zardoz的程序。

    第一步,更新你的configure.in文件以包含automake所需的命令。最简单的办法就是在AC_INIT后加一个AM_INIT_AUTOMAKE调用:

    AM_INIT_AUTOMAKE(zardoz, 1.0)

    如果你的程序没有任何复杂的因素。这是最简单的办法。

    现在,你必须重建‘configure’文件。这样做,你必须告诉autoconf如何找到你所用的新宏。最简单的方式是使用aclocal程序来 生成你的‘aclocal.m4’.aclocal让你将你的宏加进‘acincluide.m4’,所以你只需重命名并运行他


    mv aclocal.m4 acinclude.m4

    aclocal

    autoconf


    现在是为你的zardoz写Makefile.am的时候了。zardoz是一个用户程序,所以逆向将他安装在其他用户程序安装的目录。zardoz还有 一些Texinfo文档。你的configure.in脚本使用AC_REPLACE_FUNCS,所以你需要链接‘@LIBOBJS@’

    bin_PROGRAMS = zardoz

    zardoz_SOURCES = main.c head.c float.c vortex9.c gun.c

    zardoz_LDADD = @LIBOBJS@


    info_TEXINFOS = zardoz.texi


    现在你可以运行Automake以生成你的Makefile.in文件。

    一个经典的程序

    hello 以其简单和多面幸而闻名。着一段将显示在Hello包Automake如何被使用。

    下面是

    dnl用 autoconf 处理它以产生一个 configure 脚本.

    AC_INIT(src/hello.c)

    AM_INIT_AUTOMAKE(hello, 1.3.11)

    AM_CONFIG_HEADER(config.h)


    dnl Set of available languages

    ALL_LINGUAS="de fr es ko nl no pl pt sl sv"

    dnl Checks for programs.

    AC_PROG_CC

    AC_ISC_POSIX

    dnl Checks for libraries.

    dnl Checks for header files.

    AC_STDC_HEADERS

    AC_HAVE_HEADERS(string.h fcntl.h sys/file.h sys/param.h)

    dnl Checks for library functions.

    AC_FUNC_ALLOCA

    dnl Check for st_blksize in struct stat

    AC_ST_BLKSIZE

    dnl internationalization macros

    AM_GNU_GETTEXT

    AC_OUTPUT([Makefile doc/Makefile intl/Makefile po/Makefile.in \ src/Makefile tests/Makefile tests/hello], [chmod +x tests/hello])

    'AM_'宏由Automake(或Gettext 库)提供;其余的是Autoconf标准宏。

    top-level ‘Makefile.am’:

    EXTRA_DIST = BUGS ChangeLog.O

    SUBDIRS = doc intl po src tests

    --如你所见,这里所有的工作时在子目录中真正完成的.

    --'po' 和 'intl'目录是用 gettextize自动生成的,这里不做进一步讨论.

    在'doc/Makefile.am'文件中我们看到:

    info_TEXINFOS = hello.texi

    hello_TEXINFOS = gpl.texi

    --这已足以创建,安装和发布手册.


    这里是'tests/Makefile.am'文件:

    TESTS = hello

    EXTRA_DIST = hello.in testdata

    --脚本'hello'被configure创建,并且是唯一的测试.make check将运行它.

    最后是,'src/Makefile.am',所有的真正的工作是在这里完成的:

    bin_PROGRAMS = hello

    hello_SOURCES = hello.c version.c getopt.c getopt1.c getopt.h system.h

    hello_LDADD = @INTLLIBS@ @ALLOCA@

    localedir = $(datadir)/locale

    INCLUDES = -I../intl -DLOCALEDIR=\"$(localedir)\"


    创建一个'Makefile.in'文件

    要为一个包创建所有的'Makefile.in',在顶层目录运行automake不带参数的程序.automake将自动查找每个适当的 'Makefile.am'文件,并声称相应的'Makefile.in'文件.请注意,automake会简要的察看一下包的构成;它假定一个包在顶层 只有一个'configure.in'文件.如果你的包有多个'configure.in'文件,那么你必须在每一个含有'configure.in'文 件的目录运行一次'automake'程序.

    你可以随意送给automake一个参数;'.am'会加到变量名的后面,并且作为输入文件的文件名.这一特性通常被用来自动重建一个'过时的 ''Makefile.in'文件.注意,automake必须在最顶级的目录运行,及时仅仅为了重新生成某个子目录中的'Makefile.in'文 件.

    automake接受以下可选参数:

    -a

    --add-missing

    在某些特殊情况下,Automake需要某些公共文件存在.例如,如果'configure.in'运行 AC_CANONICAL_HOST时,就需要'config.guess'.Automake需要几个这样的文件,这一选项会让automake自动的 将一些缺失文件加进包中(如果可能的话).一般的,如果Automake告诉你某个文件不存在,你可以试一试这个选项.

    --amdir = dir

    在dir所制定的目录而不是安装目录下寻找Automake的数据文件,经常用于调试.

    --build-dir = dir

    告诉Automake创建目录在哪里.这一选项只用于将'dependencies'加进一个由make dist生成的'Makefile.in'文件中.

    --cygnus

    将使得所生成的'Makefile.in'文件遵循Cygnus规则,而不是GNU或Gnits规则.

    --foreign

    设置全局严格性为'gnits'.

    --gnu

    设置全局严格性为'gnu'.

    --help

    帮助

    -i

    --include-deps

    将所有自动创建时的依赖信息包括在'Makefile.in'中.通常在制作发行时用到.

    --generate-deps

    创建一个'.dep_segment'文件,它合并了所有的自动生成时的依赖信息,通常用于制作发行.这在维护'SMakefile'或其他平台上的 'Makefile'(如Makefile.Dos)时非常有用.它只能与--include-deps, --srcdir-name和 --build-dir 联合使用.注意这一选项不作其他任何处理.

    --no-force

    一般的,automake创建在'configure.in'中提及的所有'Makefile.in'文件.这一选项将导致,automake只更新那些对于与自身相关的东西过了时的'Makefile.in'文件.

    -o dir

    --output-dir = dir

    将生成的'Makefile.in'放在指定的dir目录中.通常,'Makefile.in'放在与之相对应的'Makefile.am'所在目录中的.在制作发布时使用.

    --srcdir-name = dir

    告诉Automake与当前创建动作相关的源文件目录的名字.个选项仅被用于将自动创建时的依赖信息包括在'Makefile.in'中.

    -v

    --verbose

    时Automake打印出正在读取或创建的文件的信息.

    --version

    打印Automake的版本号.


    扫描'configure.in'文件

    Automake扫描包的'configure.in'文件来决定某些关于包的信息.'configure.in'中必须定义一些变量并且需要一些 autoconf 宏.Automake也会利用'configure.in'文件的信息来定制它的输出.

    Automake还支持一些autoconf宏以使维护更为简单.


    配置需求

    满足Automake基本要求的最简单方法是使用'AM_INIT_AUTOMAKE'宏.但如果你愿意,你可以手工来做.


    自动生成 aclocale.m4

    Automake包括很多Autoconf宏,其中有些在某些情况下游Automake使用,这些宏必须被定义在你的'aclocal.m4'中.否则,autoconf将看不到他们.

    acloal程序将根据'configure.in'自动生成'aclocal.m4'.这提供了一个方便的途径来得到Automake提供的宏.


    Automake所支持的Autoconf宏

    AM_CONFIG_HEADER

    AM_CYGWIN32

    AM_FUNC_STRTOD

    AM_FUNC_ERROR_AT_LINE

    AM_FUNC_MKTIME

    AM_FUNC_OBSTACK

    AM_C_PROTOTYPES

    AM_HEADER_TOCGWINSZ_NEEDS_SYS_IOCTL

    AM_INIT_AUTOMAKE

    AM_PATH_LISPDIR

    AM_PROG_CC_STDC

    AM_PROG_INTALL

    AM_PROG_LEX

    AM_SANITY_CHECK

    AM_SYS_POSIX_TERMIOS

    AM_TYPE_PTRDIFF_T

    AM_WITH_DMALLOC

    AM_WITH_REGEX


    编写你自己的aclocal宏

    Aclocal并没有特定的宏机制,因此你可以用你自己的宏来扩展它.这以特性经常被用以制作做那些想让自己的Autoconf宏被其他应用程序使用的 库.例如,gettext库支持一个AM_GNU_GETTEXT宏,它可以被任何使用gettext库的包所使用.当该库被安装后,它会安装这个宏,这 样,aclocal就能够找到他了.

    宏的名称应以'.m4'结尾,这样的文件将被安装在'$(datadir)/aclocal'中.


    最顶层'Makefile.am'

    在non-flat包中,顶层'Makefile.am'必须告诉Automake哪些子目录将被创建.这是通过SUBDIRS变量定义的.

    SUBDIRS宏包含一个子目录列表,以规定各种创建的过程.'Makefile'中很多目标(如,all)将不止运行所在目录中,还要运行于所有指定的 子目录中.注意,在SUBDIRS中列出的目录并不需要包含'Makefile.am'文件而只需要'Makefile'文件.这将允许包含位于不使用 Automake的包(如,gettext)中的库.另外,SUBDIRS之中的目录必须是当前目录的直接子目录.比如,你不能在SUBDIRS中指定 'src/subdir'.

    在deep型的包中,顶层'Makefile.am'文件通常非常短.例如,下面是HELLO发布中的'Makefile.am':


    EXTRA_DIST = BUGS ChangeLog.O README-alpha

    SUBDIRS = doc intl po src tests


    如果你只想创建一个包的子集,你可以覆盖SUBDIRS(就如同GNU inetutils的情形)在你的'Makefile.am'中包括:

    SUBDIRS = @SUBDIRS@

    让后在你的'configure.in'中你可以指定:


    SUBDIRS = "src doc lib po"

    AC_SUBST(SUBDIRS)


    这样做的结果是automake被欺骗了,它会在指定目录创建包,但并不真正绑定那个列表直到运行configure.


    SUBDIRS可以包含配置的替代(如,'@DIRS@');Automake 自己并不真正检测这一变量的内容.

    如果 SUBDIRS 被定义了,那么你的'configure.in' 就必须包含AC_PROG_MAKE_SET.

    SUBDIRS 的使用并不局限于顶层'Makefile.am'.Automake 可被用来构造任意深度的包.


    参考文献

    Automake.htm

    [目录]


    diff

    diff

      diff是生成源码补丁的必备工具。其命令格式为:

     diff [命令行选项] 原始文件 新文件

    常用命令行选项如下:

       -r 递归处理目录     -u 输出统一格式(unified format)

       -N patch里包含新文件   -a patch里可以包含二进制文件

      它的输出在stdout上,所以你可能需要把它重定向到一个文件。diff的输出有“传统格式”和“统一格式”之分,现在大都使用统一格式:

      传统格式示例:

       [hahalee@builder]$ diff a.txt b.txt

       1a2

       > here we insert a new line

       3d3

       <>

      统一格式示例:

       [hahalee@builder]$ diff -u a.txt b.txt

       --- a.txt Thu Apr 6 15:58:34 2000

       +++ b.txt Thu Apr 6 15:57:53 2000

       @@ -1,3 +1,3 @@

       This is line one

       +here we insert a new line

       and this is line two

       -why not this third line?

      通过比较可以看出,传统格式的patch文件比较小,除了要删除/插入的行外没有冗余信息。统一格式则保存了上下文(缺省是上下各三行,最少需要两 行),这样,patch的时候可以允许行号不精确匹配的情况出现。另外,在patch文件的开头明确地用---和+++标示出原始文件和当前文件,也方便 阅读。要选用统一格式,用 u 开关。

      通常,我们需要对整个软件包做修改,并生成一个patch文件,下面是典型的操作过程。这里就要用到前面介绍的几个命令行开关了:

      tar xzvf software.tar.gz # 展开原始软件包,其目录为software

      cp _a software software-orig # 做个修改前的备份

      cd software

      [修改,测试……]

      cd ..

      diff _ruNa software-orig software > software-my.patch

      现在我们就可以保存software-my.patch做为这次修改的结果,至于原始软件包,可以不必保存。等到下次需要再修改的时候,可以用 patch命令把这个补丁打进原始包,再继续工作。比如是在linux kernel 上做的工作,就不必每次保存几十兆修改后的源码了。这是好处之一,好处之二是维护方便,由于unified patch格式有一定的模糊匹配能力,能减少原软件包升级带来的维护工作量(见后)

    patch

      patch命令跟diff配合使用,把生成的补丁应用到现有代码上。常用命令行选项:

      patch [命令行选项] [待patch的文件[patch]]

      -pn patch level(n是数字) -b[后缀] 生成备份,缺省是.orig

    为了说明什么是patch level,这里看一个patch文件的头标记。

      diff -ruNa xc.orig/config/cf/Imake.cf xc.bsd/config/cf/Imake.cf

      --- xc.orig/config/cf/Imake.cf Fri Jul 30 12:45:47 1999

      +++ xc.new/config/cf/Imake.cf Fri Jan 21 13:48:44 2000

      这个patch如果直接应用,它会去找xc.orig/config/cf目录下的Imake.cf文件,假如你的源码树的根目录是缺省的xc而不是 xc.orig,除了mv xc xc.orig之外,有无简单的方法应用此patch呢?patch level就是为此而设:patch会把目标路径名砍去开头patch level个节(由/分开的部分)。在本例中,可以用下述命令:cd xc; patch _p1 < /pathname/xxx.patch 完成操作。注意,由于没有指定patch文件,patch程序默认从stdin读入,所以用了输入重定向。

      如果patch成功,缺省是不建备份文件的(注:FreeBSD下的patch工具缺省是保存备份),如果你需要,可以加上 b 开关。这样把修改前的文件以“原文件名.orig”的名字做备份。如果你喜欢其它后缀名,也可以用“b 后缀”来指定。

      如果patch失败,patch会把成功的patch行给patch上,同时(无条件)生成备份文件和一个.rej文件。.rej文件里是没有成功提交的patch行,需要手工patch上去。这种情况在原码升级的时候有可能会发生。

      关于二进制文件的说明:binary文件可以原始方式存入patch文件。diff可以生成(加-a选项),patch也可以识别。如果觉得这样的patch文件太难看,解决方法之一是用uuencode处理该binary文件。

    [目录]


    rcs

      单个文件的版本控制/管理,适合对少量文件进行版本控制,不适合小组进行项目协作开发。优点:使用简便;缺点:功能有限。RCS常用命令有ci/co/rcsdiff。

      rcs用一个后缀为“,v”的文件保存一文件的内容和所有修改的历史信息,你可以随时取出任意一个版本,用rcs保存程序就不必为不同版本分别备份。下面是一个“,v”文件的例子:

     (太长,忽略。请看演示或自己找一个样本)

      rcs文件里记载了每次更新的日期、作者、还有更新说明(Log)。文件的最新版本内容放在Log之后,再后面是历次版本与其后一版本的差别,按 check in的时间做倒序排列。这么做的原因是因为新版本的check out机会大些,倒序排列可优化check out时间。

       ci _ check in,保存新版本

      此命令把指定文件添加到rcs历史文件中,同时把该文件删除。如果当前目录下有个RCS目录,ci会把历史文件存在此处,否则放在当前目录下。

       [hahalee@builder]$ mkdir RCS

       [hahalee@builder]$ ci wood.txt

       RCS/wood.txt,v <-- wood.txt

       enter description, terminated with single '.' or end of file:

       NOTE: This is NOT the log message!

       >> initial checkin #NOTE: 这里是给本次checkin做的说明

       >> .

       initial revision: 1.1

       done

       [hahalee@builder]$ ls -l RCS/

       总共 4

       -r--r--r-- 1 hahalee hahalee 451 Apr 7 07:27 wood.txt,v

      ci也有丰富的命令行选项,比如,可以指定check in的版本号,甚至可以用字符串来做版本号,请查阅ci的manpage。

       co _ check out,取出当前(或任意)版本

    常用命令行选项:

      -r[rev] 指定版本的checkout -l[rev] 指定版本,加锁的checkout

      如不加可选的版本号,缺省是最近的版本。如果只需要一份只读的拷贝,用-r(特殊情况,如需要一份只读的当前拷贝,可以不要任何选项)。如需要对checkout的文件进行修改,请用-l选项。常见的操作流程是:

       ci xxx.c; co _l xxx.c; 编辑,测试; ci xxx.c …………

      在每次checkin的时候,版本号会自动在最低位上加1。为了在文件中反映版本控制信息,rcs提供了几个特殊的关键字,这里介绍$Id$和$Log$,其它的请参考info cvs。

      $Id$代表文件的版本标识,由文件名/版本号/最后一次checkin时间/最后一次checkin的用户这几项组成,比如:

    $Id: wood.txt,v 1.3 2000/04/07 00:06:52 hahalee Exp $

    如果需要更详细的信息,可以用$Log$,$Log$被扩展为该文件的所有修改日期和备注,例:

        /* $Log: wood.txt,v $

        * Revision 1.2 2000/04/07 00:29:12 hahalee

        * This is my second checkin

        *

        * Revision 1.1 2000/04/07 00:28:39 hahalee

        * Initial revision

        * /

      顺便介绍一下ident命令。它的功能比较简单,就是从文件中搜索出RCS标示符并打印出来。可以用ident /usr/sbin/sendmail 来看看。不用说,如果想在最终的binary文件里找到$Id$串,得要把它声明到一个字符串里去。很多程序里这么写:

        #ifndef lint //这里是为了避免lint报告“变量未使用”

        static const char rcsid[] =

       "$Id: bin/sh.c,v 1.15 1999/08/27 23:13:43 wp Exp $"; //这是从 $Id$ 扩展                                                                                                                                                    //出来的

        #endif

       rcsdiff _ 比较revision之间的差异.运行diff命令,生成patch文件

    命令行格式:rcsdiff [选项] [-r版本[-r版本]] [diff选项] 文件名

      说明:如果没给出版本号,把上次checkin的内容同当前工作文件比较;如给出了一个版本号,就把那个版本的内容同当前工作文件比较;若给出了两个 版本号,则用第一个来跟第二个比较。由于rcsdiff调用diff命令,所有的diff选项都可用。它的输出也是加了额外信息的diff格式内容,可以 给patch使用。

      rcs里面还有rcs,rcsclean,rlog,merge,rcsmerge我们没有提到,有的特别简单有的特别繁琐且用得少。其中rcs命令可以用来对rcs文件进行各种精细的维护,最为复杂。


    [目录]


    内核重编译常见故障

    该程序只是简单地录一小段纯音频数据存储在test.wav中,用命令cat test.wav >/dev/audio就可以播放出来。因为程序要读写声卡的设备文件,所以你必须用相应的权限,或者你是root

    #include
    #include
    #include
    #include
    #include

    main()
    {
    int id,fd,i,j;
    char testbuf[4096];
    id=open("/dev/audio",O_RDWR);
    //这里使用声卡驱动程序,linux里当作一个文件读写
    fd=open("test.wav",O_RDWR);
    //一个文件

    if(id<0||fd<0){
    printf("error in open the device!\n");
    printf("id %d\t dsp%d\t seq%d\n",id,dsp,seq);
    exit(-1);
    }

    post=0;
    ioctl(id,SNDCTL_DSP_RESET,(char *)&i);
    ioctl(id,SNDCTL_DSP_SYNC,(char *)&i);
    i=1;
    ioctl(id,SNDCTL_DSP_NONBLOCK,(char *)&i);
    i=8000;
    ioctl(id,SNDCTL_DSP_SPEED,(char *)&i);
    i=1;
    ioctl(id,SNDCTL_DSP_CHANNELS,(char *)&i);
    i=8;
    ioctl(id,SNDCTL_DSP_SETFMT,(char *)&i);
    i=3;
    ioctl(id,SNDCTL_DSP_SETTRIGGER,(char *)&i);
    i=3;
    ioctl(id,SNDCTL_DSP_SETFRAGMENT,(char *)&i);
    i=1;
    ioctl(id,SNDCTL_DSP_PROFILE,(char *)&i);

    for(j=0;j<10;){
    i=read(id,testbuf,4096);
    if(i>0){
    write(fd,filebuf,i);
    j++;
    }
    }

    close(fd);
    close(id);

    }

    [目录]


    cvs

      CVS,顾名思义,是个可以用在小组协作环境下的源码版本管理系统。同类的软件有AT&T的SCCS(Source Code Control System),还有PVCS等。在OpenSource项目里CVS用得最为广泛, Linux kernel不使用CVS来维护,下面我们将会参考FreeBSD的源码管理来做示例。CVS是个相当复杂的系统,FreeBSD甚至设有专门的“CVS 管理员”(CVS “Maester”)来全面负责项目的CVS repository维护。

      下面介绍与CVS相关的若干概念和术语:

        Repository:   “源码仓库”,CVS存放的项目源码历史档案

        CVSROOT:     该环境变量指明CVS Repository存放的目录

        Module:      模块。就是CVSROOT下的顶级目录名

        Vendor Branch:  分支。在一套Repository里可以存放多个代码分支的历史

        Release Tag:   发行标记。对于每一个版本,可以用符号来做标记

      下面是一个CVS repository的版本衍生图,大致描绘了FreeBSD的版本发行情况。图中的RELENG_3和RELENG_4表示“Release Engine”,也就是Vendor Branch,每个Branch分头发展,等某个Branch的开发到了一定的质量水准,就做个Release Tag。比如最近的4.0-RELEASE的Release Tag是REL_4_0。

      这些不同的Branch都存放在同一个Repository Tree里。

      CVS是个很复杂的系统,可以参考下面两个URL获得进一步的信息:

    http://www.cyclic.com

    http://www.loria.fr/~molli/cvs-index.html

    (在cvs软件包里含有详细的文档,应当查阅info版本。几个ps文件都太老了)

    下面介绍CVS的基本用法。

    ① Import 导入/创建CVS Repository Tree

      首先建一个目录作为你的CVSROOT,然后用cvs init命令对其初始化(建立一系列log,config文件)。然后到工作目录下使用cvs import命令:

       [hahalee@builder]$ mkdir /home/hahalee/CVS

       [hahalee@builder]$ export CVSROOT=/home/hahalee/CVS

       [hahalee@builder]$ cvs init

       [hahalee@builder]$ cvs import _b 0.5.0 hftpd RELENG_0 REL_0

       N hftpd/tar.h

       N hftpd/auth.h

       [blah...blah...blah...]

       N hftpd/docs/rfcs/rfc0959.txt

       N hftpd/docs/rfcs/rfc2428.txt

       No conflicts created by this import

      上述操作在$CVSROOT下生成hftpd目录,可以看到里面都是后缀为“,v”的文件,这就是import进来的Repository。 RELENG_0是vendor-tag,REL_0是release-tag。vendor-tag就是vendor branch tag,可以理解为”code name”。

    ② Checkout 创建私有工作目录/Export

      换一个空目录,运行 cvs checkout modules_name 即可:

       [hahalee@builder]$ cvs checkout hftpd# hftpd是我们的module name

       cvs checkout: Updating hftpd

       U hftpd/AUTHORS

       U hftpd/COPYING

       [blah blah blah] # 省略许多

       [hahalee@builder t]$ ls -l

       总共 0

       drwxrwxr-x 5 hahalee hahalee 1253 Apr 7 20:08 hftpd

       [hahalee@builder t]$ find ./ -type d

      从最后一条命令的输出可看到,checkout的工作目录里多了CVS目录。里面记载了CVS相关的信息,可以方便后续的cvs操作。如果纯粹是为了拷贝出最新的source tree,可以用export,此时不会建立CVS目录。

    ③ Update 更新

      当你完成某一部分代码的时候,先不忙提交,可以把别人可能做了的其他修改update过来然后统一编译调试无误后再提交,这是team work的准则。在checkout出来的工作目录下(不管什么子目录),直接cvsup update 就可以了,当然你要先把CVSROOT环境变量设置好。

    ④ Commit 提交

      很简单,cvs commit。但你必须要在checkout出来的工作目录里提交才行:

       [hahalee@builder]$ cvs commit

       cvs commit: Examining .

       cvs commit: Examining docs

       cvs commit: Examining docs/man

       cvs commit: Examining docs/rfcs

       cvs commit: Examining tools

       Checking in AUTHORS;

       /home/hahalee/CVS/hftpd/AUTHORS,v <-- AUTHORS

       new revision: 0.6; previous revision: 0.5

       done

      关于并发提交冲突:任何用户可以随意checkout他们自己的工作拷贝,commit也是不受限制的。这样,当用户a和b分别checkout了1.2版的c.c,然后各自对c.c做了修改,a提交了他的修改,然后,当b提交的时候,冲突就产生了。

      这时候,cvs会做以下动作:

    告诉用户b,对c.c的提交发生冲突
    对用户b当前的c.c做备份文件.#c.c.1.2
    试图合并a和b的修改,生成新的c.c
    然后,用户b应当修改c.c,去掉/合并冲突的行,并以版本1.4提交。

    ⑤ Diff

      可以用类似rcsdiff的方法用cvs生成patch,命令行语法也类似

       [hahalee@builder]$ cvs diff -u -r0.5 AUTHORS

       Index: AUTHORS

       ===================================================================

       RCS file: /home/hahalee/CVS/hftpd/AUTHORS,v

       retrieving revision 0.5

       retrieving revision 0.6

       diff -u -r0.5 -r0.6

       --- AUTHORS 2000/04/07 10:46:02 0.5

       +++ AUTHORS 2000/04/07 14:05:57 0.6

       @@ -1,3 +1,4 @@

       +ah! let me in!

       So then, who can't spell

       Develloppopotamus?

       Quite a lot of us.

      还有一个rdiff,用来生成两个不同的release 之间的patch。

    ⑥ 其他操作

       cvs 的其他操作还包括有:

       admin 管理功能

       tag 对某一版本做符号标记

       release 取消checkout,删除工作目录(release在这里是“释放”的意思)

       add,remove 往repository里添加/删除文件

       history 查看repository操作历史记录

    ⑦ CVS 的多平台特性以及C/S扩展

      cvs是多平台的,开发可以在多种平台比如,可以把linux上的CVS Repository通过samba export出来在Windows平台上做开发。现在很多软件包里包含有*NIX/Windows/MacOS等多平台支持代码,cvs的跨平台特性可提 供最好的多平台开发支持。

      不过,cvs的操作是直接基于文件系统的,在需要大量远程协作的场合问题很多,远程的NFS mount效率太差,也会有安全问题。新版本的cvs自身内建了Client/Server支持,也可以利用Unix上传统的远程交互手段来通讯。

      1,通过rsh(也可用ssh替换)

      2,使用cvs自带的C/S用户认证:pserver(缺省端口2401)

      3,使用kerberos的gserver、kserver


    [目录]


    共享库工具

    strip:
    nm:
    size:
    string:

    1 创建和使用静态库

    创建一个静态库是相当简单的。通常使用 ar 程序把一些目标文件(.o)组合在一起,
    成为一个单独的库,然后运行 ranlib,以给库加入一些索引信息。

    2 创建和使用共享库
    特殊的编译和连接选项
    -D_REENTRANT 使得预处理器符号 _REENTRANT 被定义,这个符号激活一些宏特性。
    -fPIC 选项产生位置独立的代码。由于库是在运行的时候被调入,因此这个
    选项是必需的,因为在编译的时候,装入内存的地址还不知道。如果
    不使用这个选项,库文件可能不会正确运行。
    -shared 选项告诉编译器产生共享库代码。
    -Wl,-soname -Wl 告诉编译器将后面的参数传递到连接器。而 -soname 指定了
    共享库的 soname。


    # 可以把库文件拷贝到 /etc/ld.so.conf 中列举出的任何目录中,并以
    root 身份运行 ldconfig;或者
    # 运行 export LD_LIBRARY_PATH='pwd',它把当前路径加到库搜索路径中去。


    3 使用高级共享库特性
    1> ldd 工具
    ldd 用来显示执行文件需要哪些共享库, 共享库装载管理器在哪里找到了需要的共享库.

    2> soname

    共享库的一个非常重要的,也是非常难的概念是 soname--简写共享目标名(short for shared object name)。这是一个为共享库(.so)文件而内嵌在控制数据中的名字。如前面提到的,每一个程序都有一个需要使用的库的清单。这个清单的内容是一系列库 的 soname,如同 ldd 显示的那样,共享库装载器必须找到这个清单。

    soname 的关键功能是它提供了兼容性的标准。当要升级系统中的一个库时,并且新库的 soname 和老的库的 soname 一样,用旧库连接生成的程序,使用新的库依然能正常运行。这个特性使得在 Linux 下,升级使用共享库的程序和定位错误变得十分容易。

    在 Linux 中,应用程序通过使用 soname,来指定所希望库的版本。库作者也可以通过保留或者改变 soname 来声明,哪些版本是相互兼容的,这使得程序员摆脱了共享库版本冲突问题的困扰。

    查看/usr/local/lib 目录,分析 MiniGUI 的共享库文件之间的关系

    3> 共享库装载器

    当程序被调用的时候,Linux 共享库装载器(也被称为动态连接器)也自动被调用。它的作用是保证程序所需要的所有适当版本的库都被调入内存。共享库装载器名字是 ld.so 或者是 ld-linux.so,这取决于 Linux libc 的版本,它必须使用一点外部交互,才能完成自己的工作。然而它接受在环境变量和配置文件中的配置信息。

    文件 /etc/ld.so.conf 定义了标准系统库的路径。共享库装载器把它作为搜索路径。为了改变这个设置,必须以 root 身份运行 ldconfig 工具。这将更新 /etc/ls.so.cache 文件,这个文件其实是装载器内部使用的文件之一。

    可以使用许多环境变量控制共享库装载器的操作(表1-4+)。

    表 1-4+ 共享库装载器环境变量
    变量 含义
    LD_AOUT_LIBRARY_PATH 除了不使用 a.out 二进制格式外,与 LD_LIBRARY_PATH 相同。
    LD_AOUT_PRELOAD 除了不使用 a.out 二进制格式外,与 LD_PRELOAD 相同。
    LD_KEEPDIR 只适用于 a.out 库;忽略由它们指定的目录。
    LD_LIBRARY_PATH 将其他目录加入库搜索路径。它的内容应该是由冒号
    分隔的目录列表,与可执行文件的 PATH 变量具有相同的格式。
    如果调用设置用户 ID 或者进程 ID 的程序,该变量被忽略。
    LD_NOWARN 只适用于 a.out 库;当改变版本号是,发出警告信息。
    LD_PRELOAD 首先装入用户定义的库,使得它们有机会覆盖或者重新定义标准库。
    使用空格分开多个入口。对于设置用户 ID 或者进程 ID 的程序,
    只有被标记过的库才被首先装入。在 /etc/ld.so.perload 中指定
    了全局版本号,该文件不遵守这个限制。


    4> 使用 dlopen

    另外一个强大的库函数是 dlopen()。该函数将打开一个新库,并把它装入内存。该函数主要用来加载库中的符号,这些符号在编译的时候是不知道的。比如 Apache Web 服务器利用这个函数在运行过程中加载模块,这为它提供了额外的能力。一个配置文件控制了加载模块的过程。这种机制使得在系统中添加或者删除一个模块时,都 不需要重新编译了。

    可以在自己的程序中使用 dlopen()。dlopen() 在 dlfcn.h 中定义,并在 dl 库中实现。它需要两个参数:一个文件名和一个标志。文件名可以是我们学习过的库中的 soname。标志指明是否立刻计算库的依赖性。如果设置为 RTLD_NOW 的话,则立刻计算;如果设置的是 RTLD_LAZY,则在需要的时候才计算。另外,可以指定 RTLD_GLOBAL,它使得那些在以后才加载的库可以获得其中的符号。

    当库被装入后,可以把 dlopen() 返回的句柄作为给 dlsym() 的第一个参数,以获得符号在库中的地址。使用这个地址,就可以获得库中特定函数的指针,并且调用装载库中的相应函数。


    [目录]


    代码优化

    Linux 是一个多用户系统,因此对用户的管理是系统管理的基本组成部分。安装 Linux 的用户很可能就是该系统的管理员,也就是权限最高的 root。通过对用户的管理,分清了用户之间的责、权、利,保证了系统安全。

    对软件的评价:代码的稳定性、友好性、代码的易读性、统一的风格、技巧。

    1。尽量少的使用全局变量

    2。局部变量一定要初始化,特别是指针变量

    3。成员函数功能单一,不要过分追求技巧,函数体不要过长。

    {i = i + j; {int k = i;

    j = i - j; i=j;

    i = i = j; j=k;

    } }

    4。最好有头文件

    5。关于变量名的长短问题

    6。设计函数时考虑到通用性

    7。申请内存时,一定先要释放。注意 if 问题。

    8。对浮点数比较大小时注意不要使用 ==

    9。最好不要用 goto 语句

    10。所有成员函数要单出口单入口

    11。函数中,要先检验参数的合法性

    12。最好所有的函数都有返回值,表明错误的原因。

    13。注释问题

    14。类型转化一律用显示转换。

    15。定义宏说,参数使用括号,结果也应该括起来

    #define SUB(a,b) ((a)-(b))

    3*SUB(3,4-5);

    16。变量长度一定要用 sizeof 来求

    17。malloc 后千万别忘 free 及使指针等于 NULL。

    18。字符串拷贝时尽量少使用 sprintf,而使用 memcpy,最后别忘加上'\0'

    19。慎重选择编译时的优化选项。

    20。小组开发时,注意代码风格的统一。

    [目录]


    GNU 编码标准

    GNU 编码标准(Coding Standards)

    原文在

    http://gnu.clinux.org/prep/standards.html
    看过之后随手记录了一点儿

    Linux 命令行参数处理,
    getopt();
    getopt_long();
    getopt_long_only();

    在调用过程中多使用高层的接口;
    eg. readdir;

    signal handling facilities 信号处理:
    1. BSD: signal the Best
    #include
    2. POSIX: sigaction
    3. USG: signal

    使用临时文件,请检查环境变量TMPDIR
    使用由它指定的目录

    编码格式:
    or, if you want to use ANSI C, format the definition like this:

    static char *
    concat (char *s1, char *s2)
    {
    ...
    }

    In ANSI C, if the arguments don't fit nicely on one line, split it
    like this:

    int
    lots_of_args (int an_integer, long a_long, short a_short,
    double a_double, float a_float)
    ...

    Try to avoid having two operators of different precedence at the same
    level of indentation. For example, don't write this:

    mode = (inmode[j] == VOIDmode
    || GET_MODE_SIZE (outmode[j]) > GET_MODE_SIZE (inmode[j])
    ? outmode[j] : inmode[j]);

    Instead, use extra parentheses so that the indentation shows the
    nesting:

    mode = ((inmode[j] == VOIDmode
    || (GET_MODE_SIZE (outmode[j]) > GET_MODE_SIZE (inmode[j])))
    ? outmode[j] : inmode[j]);

    Insert extra parentheses so that Emacs will indent the code properly.
    For example, the following indentation looks nice if you do it by hand,


    v = rup->ru_utime.tv_sec*1000 + rup->ru_utime.tv_usec/1000
    + rup->ru_stime.tv_sec*1000 + rup->ru_stime.tv_usec/1000;

    but Emacs would alter it. Adding a set of parentheses produces something
    that looks equally nice, and which Emacs will preserve:

    v = (rup->ru_utime.tv_sec*1000 + rup->ru_utime.tv_usec/1000
    + rup->ru_stime.tv_sec*1000 + rup->ru_stime.tv_usec/1000);

    Format do-while statements like this:

    do
    {
    a = foo (a);
    }
    while (a > 0);

    清洗的使用C语言的构造:
    1.不要省略int类型的声明;
    2.-Wall
    3.不要在函数内部进行extern声明;
    4.在函数中使用另外的形参命名方式;
    5.不要在局部变量和参数中映射全局变量;

    变量和函数的命名方法:
    1.在定义全局变量和函数的时候,不要使用过于简单的命名方法,要通过名字反映它
    们的用途;
    2.不要过分使用缩写;

    3.使用下划线来分割名字中的单词;
    4.使用枚举类型来定义constant int,而不要用#define

    不同系统间的可移植性:
    1.使用Autoconf来进行配置;
    2.define the "feature test macro" _GNU_SOURCE when compiling your C
    files.

    调用系统函数:
    1.不要使用sprintf的返回值;
    2.vfprintf不是都提供的;
    3.main要返回int;
    4.不要明确的声明系统函数;
    5.如果必须定义系统函数的话,不要指明参数类型;
    6.对于处理string的函数需要特别对待;

    i18n,国际化:
    1.要在每一个程序中使用gettext库;
    eg. printf (gettext ("Processing file `%s'..."));

    程序的文档化:

    发布过程:


    Makefile约定:

    [目录]


    书籍

    "Beginning Linux Programming"

    published by Wrox Press
    author: Neil Matthew and Richard Stones

    "LINUX kernel Internals"

    published by Addison Wesley

    "Advanced Programming Language in the UNIX Environment"

    published by Addison Wesley
    ISBN 0-201-56317-7

    author: W. Richard Stevens

    "UNIX Network Programming"

    published by Prentice Hall
    ISBN 981-3026-53-7
    author: W. Richard Stevens

    "The UNIX C Shell field guide"

    published by Prentice-Hall
    ISBN 0-13-937468-X 025

    [目录]