Saturday, December 06, 2008

C++动态分配和释放多维数组

假设需要一个A x B x C的三维数组

方法1
分配:
int *** a = new int ** [A];
for(int i = 0; i < A; ++i){
a[i] = new int * [B];
for(int j = 0; j < B; ++j)
a[i][j] = new int[C];
}

释放:
for(int i = 0; i < A; ++i){
for(int j = 0; j < B; ++j)
delete [] (a[i][j]);
delete []a[i];
}
delete [] a;
a = NULL;

方法2
分配:
int (*a)[B][C] = new int [A][B][C];
释放:
delete [] a;
a = NULL;

C中方法类似

简单时间测试:
方法2更快
原因:malloc和 new操作的时间开销大,其开销以调用次数为主要衡量标准

Sunday, November 23, 2008

[转]GNU/Linux平台的C程序开发及程序运行环境

转自http://blog.chinaunix.net/u/13991/showart.php?id=104262


本文介绍在GNU/Linux环境下一个C程序由源代码到程序,到加载运行,最后终止的过程。同时以此过程为载体,介绍GNU/Linux平台下软件开发工具的使用。


本文以我们最常见的hello, world!为例:

#include

main ()

{

printf(“hello, world!\n”);

}

C程序生成


下图是一个由C源代码转化为可执行文件的过程:

代码编辑: 比较流行的编辑器是GNU EmacsvimEmacs具有非常强大的功能,并且可扩展。

编译:包括编译预处理,编译,汇编,连接过程。它们都可以通过GCC来实现。关于GCC,可以参考我关于GCC的笔记


C编译器将源文件转换为目标文件,如果有多个目标文件,编译器还将它们与所需的库相连接,生成可执行模块。当程序执行时,操作系统将可执行模块拷贝到内存中的程序映象。


程序又是如何执行的呢?执行中的程序称之为进程。程序转化为进程的步骤如下:


1, 内核将程序读入内存,为程序镜像分配内存空间。

2, 内核为该进程分配进程标志符(PID)。

3, 内核为该进程保存PID及相应的进程状态信息。

经过上述步骤,程序转变为进程,即可以被调度执行。

上述的hello, world程序实际是不规范的,POSIX规定main函数的原型为:

int main( int argc, char *argv[])

argc是命令行参数的个数,argv是一个指针数组,每个指针元素指向一个命令行参数。

e.g: $ ./a.out arg1 arg2

argc = 4

argv[0] = ./a.out argv[1] = arg1 argv[2] = arg2



C程序的开始及终止


程序的运行:

唯一入口:exec函数族(包括execl, execv, execle, execve, execlp, execvp


程序开始执行时,在调用main函数之前会运行C启动例程,该例程将命令行参数和环境变量从内核传递到main函数。

程序的终止:有8种途径:

正常终止

1, main返回。

2, 调用exit

3, 调用_exit_Exit

4, 从最后一个线程的开始例程返回。

异常终止

5, 调用abort

6, 接收到一个终止信号。

7, 对最后一个线程发出的取消请求做出响应。


_exit_Exit的区别 :前者由POSIX定义,后者由ISO C定义。

exit_exit, _Exit的区别:前者在退出时会调用由用户定义的退出处理函数,而后两者直接退出. (关于退出处理函数atexit(), 参考APUE2, P182.)


另外, 调用exit()或_Exit()需要包含, 调用_exit()需要包含.



要退出程序,除了return只能在main中调用外,exit, _exit, _Exit可以在任意函数中调用。


main函数最后调用return (0); 与调用exit (0)是等价的。


程序中调用exit时,exit首先调用注册的退出处理函数(通过atexit注册),然后关闭所有的文件流。

在程序运行结束时,main函数会向调用它的父进程(shell)返回一个整数值,称之为返回状态。该数值由exitreturn定义。如果没有显示地调用它们,程序还是会正常终止,但返回数值不确定(以前面的hello, world程序为例,返回值为13,实际上是printf函数的字符个数)。


$ gcc -Wall -o hello hello.c
$ ./hello
$ echo $? (echo $? 用于在bash中查看子程序的返回值)
13


程序映象


我们已经了解了一个可执行模块(executable module)是怎样由源代码生成的. 那么, 执行这个程序时, 又是怎样的情况呢? 下面介绍一个位于磁盘中的可执行程序是如何被执行的.


(1) 程序被执行时, 操作系统将可执行模块拷贝到内存的程序映像(program image)中去.

(2) 正在执行的程序实例被称为进程: 当操作系统向内核数据结构中添加了适当的信息, 并为运行程序代码分配了必要的资源之后, 程序就变成了进程. 这里所说的资源就包括分配给进程的地址空间和至少一个被称为线程(thread)的控制流.


上面只是大而化之地介绍了程序是如何转化为进程的, 这里关注的是内存程序映像. 在第(1)步中, 操作系统将可执行模块由硬盘拷贝到内存的程序映像中, 程序映像的一般布局如下图:

从低地址到高地址依次为下列段:

1, 代码段:即机器码,只读,可共享(多个进程共享代码段)。

2, 数据段:储存已被初始化了的静态数据。

3, 未初始化的数据段(也被称为BSS段):储存未始化的静态数据。

4, 堆:储存动态分配的内存.

5, 栈:储存函数调用的上下文, 动态数据.


另外, 在高地址还储存了命令行参数及环境变量.


程序代码(text)段一般是在进程之间共享的. 比如一个进程fork出一个子进程时, 父子进程共享text段, 子进程获得父进程数据段, 堆, 栈的拷贝.

磁盘映像, 内存映像, 地址空间之比较

前面提到, 可执行程序首先被操作系统从磁盘中拷贝到内存中, 还要为进程分配地址空间. 加上已经介绍的内存程序映像, 这就有三种关于可执行程序的存储组织了:

磁盘: 可执行文件段 内存: 内存程序映像 进程: 进程地址空间

下标列出了它们之间的对应关系:
内存程序映像 进程地址空间
可执行文件段
code(text)
code(text)
code(text)
data data data
bss data bss
heap data
-
stack
stack
-

内存程序映像和进程地址空间之比较

(1) 它们的代码段和栈相互对应.
(2) 内存程序映像的data, bss, heap对应到进程地址空间的data段. 也就是说, data, bss, heap会位于一个连续的地址空间中, code和stack可能位于另外的地址空间. 这就可以针对不同的段实现不同的内存管理策略: code段所在的地址空间可以是"只能被执行的", data, bss, heap所在的地址空间是不可执行的...

正 因为内存程序映像中的各段可能位于不同的地址空间中, 它们不一定位于连续的内存块中. 操作系统将程序映像映射到地址空间时, 通常将内存程序映像划分为大小相同的块(也就是page, 页). 只有该页被引用时, 它才被加载到内存中. 不过对于程序员来说, 可以视内存程序映像在逻辑上是连续的.

内存程序映像和可执行文件段之比较

(1) 明显, 前者位于内存中, 后者位于磁盘中.
(2) 内存程序映像中的code, data, bss段分别对应于可执行文件段中的code, data, bss段.
(3) 堆栈在可执行文件段中是没有的, 因为只有程序被加载到内存中运行时才会被分配堆栈.
(4) 虽然可执行文件段中包含了bss, 但bss并不被储存在位于磁盘中的可执行文件中.

使用file, ls, size, strip命令来查看相关信息

我们利用下面3个简单的例子来理清上述概念:

(1) array1.c

int a[50000] = {1, 2, 3, 4}; /* 被显式初始化为非0的静态数据 */

int main(void) { 
   a[0] = 3;
   return 0;
}

(2) array2.c

int b[50000];            /* 未被显式初始化的静态数据 */
int main(void) {
   b[0] = 3;
   return 0;
}

(3) array3.c

int c[50000] = {0,0,0,0};  /* 被显式初始化为0的静态数据 */
int main(void) {
   c[0] = 3;
   return 0;
}


array1.c中, 数组a被显式初始化为非0.

array2.c中, 数组b未被显式初始化, 但由于它是静态变量, 所以被编译器初始化为默认的值: b中所有元素被初始化为0.

array3.c中, 数组c的所有元素被显式地初始化为全0.


$ gcc -Wall -o init array1.c

$ gcc -Wall -o noinit array2.c

$ gcc -Wall -o init-0 array3.c


使用ls命令, 查看磁盘文件大小:

$ ls -l init noinit init-0

-rwxr-xr-x 1 zp zp 209840 2006-08-21 15:56 init
-rwxr-xr-x 1 zp zp 9808 2006-08-21 15:57 init-0
-rwxr-xr-x 1 zp zp 9808 2006-08-21 15:57 noinit

我们发现array1.c 生成的init可执行文件比array2.c, array3.c生成的要大大约200000字节. 而array2.c 和array3.c生成的可执行文件在大小上是一样的!


严 格地说, 上述内存程序映像中的"未初始化的静态数据"应该改称为"被初始化为全0的静态数据": 被程序员显式地初始化为0或被编译起隐式地初始化为默认的0. 而且, 只有程序被加载到内存中时, 被初始化为全0的静态数据所对应的内存空间才被分配, 同时被赋予0值.


使用size命令, 查看内存程序映像信息:

$ size init noinit init-0

text data bss dec hex filename
822 200272 4 201098 3118a init
822 252 200032 201106 31192 noinit
822 252 200032 201106 31192 init-0


size命令显示内存程序映像中的text, data, bss三个段大小, 以及这3个段大小之和的十进制和十六进制表示. (由于堆栈是在程序执行时动态分配的, size无法显示它们的大小. 可以使用ps命令查看进程地址空间信息. )


通过size命令, 我们可以得知如下事实:

1, 不管静态数据是否被初始化, 加载到内存中的程序映像大小是不变的. 它们之间的区别只是data和bss段大小的不同( 影响磁盘文件的大小).

2, 由于size不计算堆栈大小, 所以ls命令和size命令类出的磁盘程序映像大小和内存程序映像大小应该是一样的, 但通过上面的ls和size命令输出我们发现:

(1) 若静态变量被初始化为非0, 磁盘映像要大于内存映像.

(2) 若静态变量被初始化为全0, 磁盘影响要小于内存影响.


这是因为:

(1) 位于磁盘中的可执行程序中不关包含上面类出的磁盘映像的内容(code, data, bss), 它还包括: 符号表, 调试信息, 针对动态库的链接表等内容. 但这些内容在程序被执行时是不会被加载到内存中的.

使用file命令可以查看可执行文件的信息. 使用strip命令可以删除可执行程序中的符号表:
$ strip init; ls -l init
-rwxr-xr-x 1 zp zp 205920 2006-08-21 16:41 init
虽然符号表被删除了, 但init中还有其他信息, 所以仍比内存镜像大.)

(2) 静态变量被初始化为全0时(不管是程序员显式地初始化还是被编译器初始化为默认的0), 这一过程是在程序被加载到内存中时进行的, 数据无非位于data和bss段中, 所以它们是否被初始化为全0对于size来说, 内存映像总的大小是不变的, 但由于磁盘映像中不包含bss的值, 所以此时磁盘映像可能小于内存映像(如果bss段大于符号表, 调试信息, 链接表等的大小).

size命令不光可以查看最终生成的可执行文件的内存映像信息, 还可以查看可.o目标文件.

进程地址空间的数据段还包括了堆, 即内存程序映像中的堆. 堆一般用作动态分配内存. ( malloc(), calloc(), realloc(), free()). 参考本blog的: C程序中的内存管理




[转]GCC笔记

转自http://www.cublog.cn/u/13991/showart.php?id=96714

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时间.

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

转自 http://www.cublog.cn/u/13991/showart.php?id=96714

[转]vim中的正则表达式

元字符 说明
. 匹配任意一个字符
[abc] 匹配方括号中的任意一个字符。可以使用-表示字符范围,
如[a-z0-9]匹配小写字母和阿拉伯数字。
[^abc] 在方括号内开头使用^符号,表示匹配除方括号中字符之外的任意字符。
\d 匹配阿拉伯数字,等同于[0-9]。
\D 匹配阿拉伯数字之外的任意字符,等同于[^0-9]。
\x 匹配十六进制数字,等同于[0-9A-Fa-f]。
\X 匹配十六进制数字,等同于[^0-9A-Fa-f]。
\w 匹配单词字母,等同于[0-9A-Za-z_]。
\W 匹配单词字母之外的任意字符,等同于[^0-9A-Za-z_]。
\t 匹配字符。
\s 匹配空白字符,等同于[ \t]。
\S 匹配非空白字符,等同于[^ \t]。
\a 所有的字母字符. 等同于[a-zA-Z]
\l 小写字母 [a-z]
\L 非小写字母 [^a-z]
\u 大写字母 [A-Z]
\U 非大写字母 [^A-Z]

表示数量的元字符
元字符 说明
* 匹配0-任意个
\+ 匹配1-任意个
\? 匹配0-1个
\{n,m} 匹配n-m个
\{n} 匹配n个
\{n,} 匹配n-任意个
\{,m} 匹配0-m个
\_. 匹配包含换行在内的所有字符
\{-} 表示前一个字符可出现零次或多次,但在整个正则表达式可以匹配成功的前提下,匹配的字符数越少越好
\= 匹配一个可有可无的项
\_s 匹配空格或断行
\_[]

元字符 说明
\* 匹配 * 字符。
\. 匹配 . 字符。
\/ 匹配 / 字符。
\\ 匹配 \ 字符。
\[ 匹配 [ 字符。

表示位置的符号
元字符 说明
$ 匹配行尾
^ 匹配行首
\< 匹配单词词首 \> 匹配单词词尾

替换变量
在正规表达式中使用 \( 和 \) 符号括起正规表达式,即可在后面使用\1、\2等变量来访问 \( 和 \) 中的内容。

懒惰模式
\{-n,m} 与\{n,m}一样,尽可能少次数地重复
\{-} 匹配它前面的项一次或0次, 尽可能地少
\| "或"操作符
\& 并列


函数式
:s/替换字符串/\=函数式
在函数式中可以使用 submatch(1)、submatch(2) 等来引用 \1、\2 等的内容,而submatch(0)可以引用匹配的整个内容。

与Perl正则表达式的区别 ?
元字符的区别
Vim语法 Perl语法 含义
\+ + 1-任意个
\? ? 0-1个
\{n,m} {n,m} n-m个
\(和\) (和) 分组

例如:
1,去掉所有的行尾空格:“:%s/\s\+$//”。“%”表示在整个文件范围内进行替换,“\s”表示空白字符(空格和制表符),“\+”对前面的字符匹配一次或多次(越多越好),“___FCKpd___0rdquo;匹配行尾(使用“\___FCKpd___0rdquo;表示单纯的“___FCKpd___0rdquo;字符);被替换的内容为空;由于一行最多只需替换一次,不需要特殊标志。这个还是比较简单的。(/)
2,去掉所有的空白行:“:%s/\(\s*\n\)\+/\r/”。这回多了“\(”、“\)”、“\n”、“\r”和 “*”。“*”代表对前面的字符(此处为“\s”)匹配零次或多次(越多越好;使用“\*”表示单纯的“*”字符),“\n”代表换行符,“\r”代表回车符,“\(”和“\)”对表达式进行分组,使其被视作一个不可分割的整体。因此,这个表达式的完整意义是,把连续的换行符(包含换行符前面可能有的连续空白字符)替换成为一个单个的换行符。唯一很特殊的地方是,在模式中使用的是“\n”,而被替换的内容中却不能使用“\n”,而只能使用“\r”。原因是历史造成的,详情如果有兴趣的话可以查看“:help NL-used-for-Nul”。
3,去掉所有的“//”注释:“:%s!\ s*//.*!!”。首先可以注意到,这儿分隔符改用了“!”,原因是在模式或字符串部分使用了“/”字符,不换用其他分隔符的话就得在每次使用“/”字符本身时写成“\/”,上面的命令得写成“:%s/\s*\/\/.*//”,可读性较低。命令本身倒是相当简单,用过正则表达式的人估计都知道“.”匹配表示除换行符之外的任何字符吧。
4,去掉所有的“/* */”注释:“:%s!\s*/\*\_.\{-}\*/\s*! !g”。这个略有点复杂了,用到了几个不太常用的 Vim 正则表达式特性。“\_.”匹配包含换行在内的所有字符;“\{-}”表示前一个字符可出现零次或多次,但在整个正则表达式可以匹配成功的前提下,匹配的字符数越少越好;标志“g”表示一行里可以匹配和替换多次。替换的结果是个空格的目的是保证像“int/* space not necessary around comments */main()”这样的表达式在替换之后仍然是合法的。

:g/^\s*$/d 删除只有空白的行
:s/\(\w\+\)\s\+\(\w\+\)/\2\t\1 将 data1 data2 修改为 data2 data1
:%s/\(\w\+\), \(\w\+\)/\2 \1/ 将 Doe, John 修改为 John Doe
:%s/\/\=line(".") 将各行的 id 字符串替换为行号
:%s/\(^\<\w\+\>\)/\=(line(".")-10) .".". submatch(1)
将每行开头的单词替换为(行号-10).单词的格式,如第11行的word替换成1. word
排序 :/OB/+1,$!sort

转自 http://blog.csdn.net/menghun_99520/archive/2008/06/06/2517970.aspx

Friday, April 11, 2008

python安全转编码

在对string进行decode时,有时会遇到错误的字串,导致解码失败,所以写了个安全解码函数
第一个是利用exception中的信息写的,第二个是最初用土办法写的
简单测了下,前者较后者大概有10%性能提高
def conv(s, decoding='gbk', encoding=''):
 while True:
  try:
   ustr = s.decode(decoding)
  except Exception, e:
   s = s[:e.start]+s[e.end:]
  else:
   if encoding:
    return ustr.encode(encoding)
   else:
    return ustr

def conv(s, decoding='gbk', encoding=''):
 flag = False
 l = []
 i = 0
 while i < len(s):
  if flag:
   try:
    u = (ch+s[i]).decode(decoding)
   except:
    flag = False
    i+=1
   else:
    flag = False
    l.append(u)
    i+=1
  elif ord(s[i]) > 0x80:
   ch = s[i]
   flag = True
   i+=1
  else:
   l.append(s[i].decode('gbk'))
   i+=1
 if not encoding:
  result = ''.join(l)
 else:
  result = ''.join(l).encode(encoding)
 return result

pydev的注释快捷键

Ctrl+3 行注释
Ctr+\ 去行注释
Ctrl+Shift+3 去行注释

Ctrl+4 块注释
Ctrl+5 去块注释

Ctrl+9 折叠全部
Ctrl+0 展开全部

Ctrl+- 折叠
Ctrl+= 展开

Ctrl+Shift+Up 上一函数
Ctrl+Shift+Down 下一函数

Ctrl+Shift+O 整理导入顺序