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

星期五, 十二月 28, 2007

gcc核心扩展

===========================
Linux 内核使用的 GNU C 扩展
===========================

GNC CC 是一个功能非常强大的跨平台 C 编译器,它对 C 语言提供了很多扩展,
这些扩展对优化、目标代码布局、更安全的检查等方面提供了很强的支持。本文把
支持 GNU 扩展的 C 语言称为 GNU C。

Linux 内核代码使用了大量的 GNU C 扩展,以至于能够编译 Linux 内核的唯一编
译器是 GNU CC,以前甚至出现过编译 Linux 内核要使用特殊的 GNU CC 版本的情
况。本文是对 Linux 内核使用的 GNU C 扩展的一个汇总,希望当你读内核源码遇
到不理解的语法和语义时,能从本文找到一个初步的解答,更详细的信息可以查看
gcc.info。文中的例子取自 Linux 2.4.18。

语句表达式
==========

GNU C 把包含在括号中的复合语句看做是一个表达式,称为语句表达式,它可以出
现在任何允许表达式的地方,你可以在语句表达式中使用循环、局部变量等,原本
只能在复合语句中使用。例如:

++++ include/linux/kernel.h
159: #define min_t(type,x,y) \
160:         ({ type __x = (x); type __y = (y); __x window_clamp, tcp_full_space(sk));

复合语句的最后一个语句应该是一个表达式,它的值将成为这个语句表达式的值。
这里定义了一个安全的求最小值的宏,在标准 C 中,通常定义为:

#define min(x,y) ((x) " "%s:%d", filename, line)

使用 ## 的原因是处理 arg 不匹配任何参数的情况,这时 arg 的值为空,GNU
C 预处理器在这种特殊情况下,丢弃 ## 之前的逗号,这样

    pr_debug("success!\n")

扩展为

    printk("" "success!\n")

注意最后没有逗号。

标号元素
========

标准 C 要求数组或结构变量的初使化值必须以固定的顺序出现,在 GNU C 中,通
过指定索引或结构域名,允许初始化值以任意顺序出现。指定数组索引的方法是在
初始化值前写 '[INDEX] =',要指定一个范围使用 '[FIRST ... LAST] =' 的形式,
例如:

+++++ arch/i386/kernel/irq.c
1079: static unsigned long irq_affinity [NR_IRQS] = { [0 ... NR_IRQS-1] = ~0UL };

将数组的所有元素初使化为 ~0UL,这可以看做是一种简写形式。

要指定结构元素,在元素值前写 'FIELDNAME:',例如:

++++ fs/ext2/file.c
41: struct file_operations ext2_file_operations = {
42:         llseek:         generic_file_llseek,
43:         read:           generic_file_read,
44:         write:          generic_file_write,
45:         ioctl:          ext2_ioctl,
46:         mmap:           generic_file_mmap,
47:         open:           generic_file_open,
48:         release:        ext2_release_file,
49:         fsync:          ext2_sync_file,
50 };

将结构 ext2_file_operations 的元素 llseek 初始化为 generic_file_llseek,
元素 read 初始化为 genenric_file_read,依次类推。我觉得这是 GNU C 扩展中
最好的特性之一,当结构的定义变化以至元素的偏移改变时,这种初始化方法仍然
保证已知元素的正确性。对于未出现在初始化中的元素,其初值为 0。

Case 范围
=========

GNU C 允许在一个 case 标号中指定一个连续范围的值,例如:

++++ arch/i386/kernel/irq.c
1062:                         case '0' ... '9': c -= '0'; break;
1063:                         case 'a' ... 'f': c -= 'a'-10; break;
1064:                         case 'A' ... 'F': c -= 'A'-10; break;

    case '0' ... '9':

相当于

    case '0': case '1': case '2': case '3': case '4':
    case '5': case '6': case '7': case '8': case '9':

声明的特殊属性
==============

GNU C 允许声明函数、变量和类型的特殊属性,以便手工的代码优化和更仔细的代
码检查。要指定一个声明的属性,在声明后写

    __attribute__ (( ATTRIBUTE ))

其中 ATTRIBUTE 是属性说明,多个属性以逗号分隔。GNU C 支持十几个属性,这
里介绍最常用的:

* noreturn

属性 noreturn 用于函数,表示该函数从不返回。这可以让编译器生成稍微优化的
代码,最重要的是可以消除不必要的警告信息比如未初使化的变量。例如:

++++ include/linux/kernel.h
47: # define ATTRIB_NORET  __attribute__((noreturn)) ....
61: asmlinkage NORET_TYPE void do_exit(long error_code)
        ATTRIB_NORET;

* format (ARCHETYPE, STRING-INDEX, FIRST-TO-CHECK)

属性 format 用于函数,表示该函数使用 printf, scanf 或 strftime 风格的参
数,使用这类函数最容易犯的错误是格式串与参数不匹配,指定 format 属性可以
让编译器根据格式串检查参数类型。例如:

++++ include/linux/kernel.h?
89: asmlinkage int printk(const char * fmt, ...)
90:         __attribute__ ((format (printf, 1, 2)));

表示第一个参数是格式串,从第二个参数起根据格式串检查参数。

* unused

属性 unused 用于函数和变量,表示该函数或变量可能不使用,这个属性可以避免
编译器产生警告信息。

* section ("section-name")

属性 section 用于函数和变量,通常编译器将函数放在 .text 节,变量放在
.data 或 .bss 节,使用 section 属性,可以让编译器将函数或变量放在指定的
节中。例如:

++++ include/linux/init.h
78: #define __init          __attribute__ ((__section__ (".text.init")))
79: #define __exit          __attribute__ ((unused, __section__(".text.exit")))
80: #define __initdata      __attribute__ ((__section__ (".data.init")))
81: #define __exitdata      __attribute__ ((unused, __section__ (".data.exit")))
82: #define __initsetup     __attribute__ ((unused,__section__ (".setup.init")))
83: #define __init_call     __attribute__ ((unused,__section__ (".initcall.init")))
84: #define __exit_call     __attribute__ ((unused,__section__ (".exitcall.exit")))

连接器可以把相同节的代码或数据安排在一起,Linux 内核很喜欢使用这种技术,
例如系统的初始化代码被安排在单独的一个节,在初始化结束后就可以释放这部分
内存。

* aligned (ALIGNMENT)

属性 aligned 用于变量、结构或联合类型,指定变量、结构域、结构或联合的对
齐量,以字节为单位,例如:

++++ include/asm-i386/processor.h
294: struct i387_fxsave_struct {
295:         unsigned short  cwd;
296:         unsigned short  swd;
297:         unsigned short  twd;
298:         unsigned short  fop;
299:         long    fip;
300:         long    fcs;
301:         long    foo;
......
308: } __attribute__ ((aligned (16)));

表示该结构类型的变量以 16 字节对齐。通常编译器会选择合适的对齐量,显示指
定对齐通常是由于体系限制、优化等原因。

* packed

属性 packed 用于变量和类型,用于变量或结构域时表示使用最小可能的对齐,用
于枚举、结构或联合类型时表示该类型使用最小的内存。例如:

++++ include/asm-i386/desc.h
51: struct Xgt_desc_struct {
52:         unsigned short size;
53:         unsigned long address __attribute__((packed));
54: };

域 address 将紧接着 size 分配。属性 packed 的用途大多是定义硬件相关的结
构,使元素之间没有因对齐而造成的空洞。

当前函数名
==========

GNU CC 预定义了两个标志符保存当前函数的名字,__FUNCTION__ 保存函数在源码
中的名字,__PRETTY_FUNCTION__ 保存带语言特色的名字。在 C 函数中,这两个
名字是相同的,在 C++ 函数中,__PRETTY_FUNCTION__ 包括函数返回类型等额外
信息,Linux 内核只使用了 __FUNCTION__。

++++ fs/ext2/super.c
98: void ext2_update_dynamic_rev(struct super_block *sb)
99: {
100:         struct ext2_super_block *es = EXT2_SB(sb)->s_es;
101:
102:         if (le32_to_cpu(es->s_rev_level) > EXT2_GOOD_OLD_REV)
103:                 return;
104:
105:         ext2_warning(sb, __FUNCTION__,
106:                      "updating to rev %d because of new feature flag, "
107:                      "running e2fsck is recommended",
108:                      EXT2_DYNAMIC_REV);

这里 __FUNCTION__ 将被替换为字符串 "ext2_update_dynamic_rev"。虽然
__FUNCTION__ 看起来类似于标准 C 中的 __FILE__,但实际上 __FUNCTION__
是被编译器替换的,不象 __FILE__ 被预处理器替换。

内建函数
========

GNU C 提供了大量的内建函数,其中很多是标准 C 库函数的内建版本,例如
memcpy,它们与对应的 C 库函数功能相同,本文不讨论这类函数,其他内建函数
的名字通常以 __builtin 开始。

* __builtin_return_address (LEVEL)

内建函数 __builtin_return_address 返回当前函数或其调用者的返回地址,参数
LEVEL 指定在栈上搜索框架的个数,0 表示当前函数的返回地址,1 表示当前函数
的调用者的返回地址,依此类推。例如:

++++ kernel/sched.c
437:                 printk(KERN_ERR "schedule_timeout: wrong timeout "
438:                        "value %lx from %p\n", timeout,
439:                        __builtin_return_address(0));

* __builtin_constant_p(EXP)

内建函数 __builtin_constant_p 用于判断一个值是否为编译时常数,如果参数
EXP 的值是常数,函数返回 1,否则返回 0。例如:

++++ include/asm-i386/bitops.h
249: #define test_bit(nr,addr) \
250: (__builtin_constant_p(nr) ? \
251:  constant_test_bit((nr),(addr)) : \
252:  variable_test_bit((nr),(addr)))

很多计算或操作在参数为常数时有更优化的实现,在 GNU C 中用上面的方法可以
根据参数是否为常数,只编译常数版本或非常数版本,这样既不失通用性,又能在
参数是常数时编译出最优化的代码。

* __builtin_expect(EXP, C)

内建函数 __builtin_expect 用于为编译器提供分支预测信息,其返回值是整数表
达式 EXP 的值,C 的值必须是编译时常数。例如:

++++ include/linux/compiler.h
13: #define likely(x)       __builtin_expect((x),1)
14: #define unlikely(x)     __builtin_expect((x),0)
++++ kernel/sched.c
564:         if (unlikely(in_interrupt())) {
565:                 printk("Scheduling in interrupt\n");
566:                 BUG();
567:         }

这个内建函数的语义是 EXP 的预期值是 C,编译器可以根据这个信息适当地重排
语句块的顺序,使程序在预期的情况下有更高的执行效率。上面的例子表示处于中
断上下文是很少发生的,第 565-566 行的目标码可能会放在较远的位置,以保证
经常执行的目标码更紧凑。

Read More...

星期三, 十二月 12, 2007

gcc的组件和软件工具

gcc安装的各个部分
 部分            描述
c++        gcc的一个版本,默认语言设置为c++,而且在连续的时候自动包含标准c++库。这和g++一样
ccl        实际的c编译程序
cclplus        实际的c
++编译程序
collect2               在不使用GNU链接程序的系统上,有必要运行collect2来产生特定的全局初始化代码
(例如c++的构造函数和析构函数)
configure               GCC源代码树根目录中的一个脚本,用于设置值和创建GCC编译程序必须的make程序的描述文件
crt0
.o        这个初始化和结束代码是为每个系统定制的,而且也被编译进该文件,该文件然后会被连接到每个可执行文件中来执行必要的启动和终止程序。
cygwinl
.dll    windows的共享库提供的API,模拟UNIX系统调用。
f77        该驱动程序可用于编译Fortran
f77l        实际的Fortran编译程序。
g
++        gcc的一个版本,默认语言设置为c++,而且在链接的时候自动包含标准c++库,这和c++一样
gcc        该驱动程序等同于执行编译程序和链接程序以产生必要的输出
gcj        该驱动程序用于编译java
gnatl        实际的Ada编译程序
gnatbind               一种工具,用于执行Ada语言的绑定
gnatlink               一种工具,用于执行Ada语言的绑定
jcl        实际的java编译程序
libgcc        该库包含的例程被作为编译程序的一部分,是因为它们可被链接到实际的可执行程序中。它们是特殊的例程,链接到可执行程序,来执行基本的任务,例如浮点运算。这些库中的例程通常都是平台相关的。
libgcj        运行时库包含所有的核心Java类
libobjc        对所有Objective
-c程序都必须的运行时的库。
libstdc
++               运行时库,包括定义为标准语言一部分的所有的c++类和函数                
                    
[b]gcc使用的工具[/b
[
b工具    [/b]     [b说明[/b
addr2line               给出一个可执行文件的内部地址,addr2line使用文件中的调试信息将地址翻译成源代码文件名和行号。该程序是binutils包的一部分
ar        这是一个程序,可通过从文档中增加、删除和析取文件来维护库文件。通常使用该工具是为了创建和管理链接程序使用的目标库文档。该程序是binutils包的一部分。
as        GNU汇编器。实际上他是一组汇编器,因为它可以被编译或能够在各个不同平台上工作。
autoconf              产生的shell脚本自动配置源代码包去编译某个特定版本的UNIX
c
++filt        程序接受被c++编译程序转换过的名字(不是被重载的),而且将该名字翻译成初始形式。该程序是binutils包的一部分
f2c        是Fortran到c的翻译程序。不是GCC的一部分。
gcov        gprof使用的配置工具,用来确定程序运行的时候哪一部分耗时最大
gdb        GNU调试器,可用于检查程序运行时的值和行为
GNATS        GNU的调试跟踪系统
(GNU Bug Tracking System)。一个跟踪GCC和其他GNU软件问题的在线系统。
gprof        该程序会监督编译程序的执行过程,并报告程序中各个函数的运行时间,可以根据所提供的配置文件来优化程序。该程序是binutils包的一部分。
ld        GNU链接程序。该程序将目标文件的集合组合成可执行程序。该程序是binutils包的一部分。
libtool        一个基本库,支持make程序的描述文件使用的简化共享库用法的脚本。
make        一个工具程序,它会读makefile脚本来确定程序中的那个部分需要编译和链接,然后发布必要的命令。它读出的脚本
(叫做makefile或Makefile)定义了文件关系和依赖关系。
nlmconv        将可重定向的目标文件转换成NetWare可加载模块
(NetWare Loadable Module NLM)。该程序是binutils包的一部分。
nm        列出目标文件中定义的符号,该程序是binutils包的一部分。
objcopy        将目标文件从一种二进制格式复制或翻译到另外一种。该程序是binutils包的一部分
objdump        显示一个或多个目标文件中保存的多种不同信息
.该程序是binutils包的一部分。
ranlib        创建的添加到ar文档的索引。该索引被ld使用来定位库中的模块。        该程序是binutils包的一部分。
ratfor        Ratfor预处理程序可由GCC激活,但不是标准GCC发布版的一部分。
size        列出目标文件中每个部分的名字和尺寸。该程序是binutils的一部分。
strings        浏览所有类型的文件,析取出用于显示调试所需的信息。        该程序binutils包的一部分。
veg        Ratfor浏览器从文本中读取信息,并以图标形式显示它们。而vcg工具并不是GCC发布的一部分,但
-dv选项可被用来产生vcg可以理解的优化数据的格式
windres        window资源文件编译程序,该程序是binutils包的一部

Read More...