- 浏览: 1400178 次
- 性别:
- 来自: 火星
文章分类
最新评论
-
aidd:
内核处理time_wait状态详解 -
ahtest:
赞一下~~
一个简单的ruby Metaprogram的例子 -
itiProCareer:
简直胡说八道,误人子弟啊。。。。谁告诉你 Ruby 1.9 ...
ruby中的类变量与类实例变量 -
dear531:
还得补充一句,惊群了之后,数据打印显示,只有一个子线程继续接受 ...
linux已经不存在惊群现象 -
dear531:
我用select试验了,用的ubuntu12.10,内核3.5 ...
linux已经不存在惊群现象
我的gcc版本是4.4.1
先来看const和define以及enum定义,编译器会做什么样的优化:
ok,我们然后来看对应的汇编代码(我这里用O2编译):
1 可以看到编译器会将它们的值直接计算然后再调用函数a,也就是+那一步被编译器优化掉了。
2 没有static修饰的const变量的那块内存被保留了,尽管foo并没有引用这块内存。
接下来看宏和内联,编译器会做什么不同的优化:
先看测试代码:
然后来看汇编代码。这里只是汇编代码·片断:
bar和foo2的代码完全一样,由此可见bar函数自动被内联了。
下面这个测试我用的gcc是4.3
现在我们来去掉static,然后来看会出现什么情况,源码:
来看汇编:
可以看到依然会被内联,可是和没有加static相比,会生成abs2这个段.这是因为编译器要做最保守的处理,如果不加static的话,abs2可能还被其他的文件所调用,因此会保留这个abs2.
这里来做个总结吧.实验代码就不贴了,有兴趣可以自己去试试.
1 用O3编译,函数加不加static ,都会被内联.
2 如果函数被调用一次,并且函数体很大
如果为static修饰的函数,他无论如何都会被内联.
如果不加static的话,就需要你强制O3优化了,用O3也会生成abs2那个段.如果O2编译,则会生成跳转指令.
3 如果函数被调用多次.
加不加static修饰的函数,调用次数超过一定的数值都会生成跳转指令.
因此我们一个文件内的内部函数尽量都声明为static的。
接下来来看数组的边界检测,编译器会如何优化:
然后来看对应的汇编代码,其中L6表示第一个赋值,L7为第二个赋值:
可以看到生成了相同的代码,边界检测被编译器优化了(也就是删除掉了).也就是循环的时候(如果我们for循环的边界检测刚好包含本身的边界检测的话,我们不需要多余的边界检测.)如果将for循环的边界检测改为大于100000的话,我们就会看到,编译器会生成相应的边界检测的.
汇编代码(只看write_to那部分):
可以看到当for的边界检测包含write_to的边界的时候就会生成这段.
再来看另外的一些边界检测优化:
来看regular的汇编代码:
可以看到i>5 && i<100被优化为 i-6 <93。并且编译器知道exit不会有返回值,因此还会设置正确的返回值。
接下来来看for和while,编译器会有什么不一样的处理:
来看生成的汇编:
可以看到完全一模一样,我记得在老的版本的gcc中,for和while生成的汇编是不一样的。
还有gcc的switch优化为跳转表可以看我前面的blog.
还有一些就是数值计算的优化了,不过这里就不介绍了。
先来看const和define以及enum定义,编译器会做什么样的优化:
enum { constant=23 }; #define CONSTANT 23 static const int Static_Constant=23; const int Constant = 23; int foo() { a(constant+3); a(CONSTANT+4); a(Static_Constant+5); return Static_Constant + Constant; }
ok,我们然后来看对应的汇编代码(我这里用O2编译):
foo: pushl %ebp movl %esp, %ebp subl $24, %esp //constant+3=26 movl $26, (%esp) call a ///CONSTANT+4=27 movl $27, (%esp) call a ///Static_Constant+5=28 movl $28, (%esp) call a ///Static_Constant + Constant = 23 + 23 = 46 movl $46, %eax leave ret .size foo, .-foo ....................................... ///被保留。 Constant: .long 23 .ident "GCC: (Ubuntu 4.4.1-4ubuntu8) 4.4.1"
1 可以看到编译器会将它们的值直接计算然后再调用函数a,也就是+那一步被编译器优化掉了。
2 没有static修饰的const变量的那块内存被保留了,尽管foo并没有引用这块内存。
接下来看宏和内联,编译器会做什么不同的优化:
先看测试代码:
#define abs(x) ((x)>0?(x):-(x)) static long abs2(long x) { return x >= 0 ? x : -x; } long foo2(long a) { return abs(a); } long bar(long a) { return abs2(a); }
然后来看汇编代码。这里只是汇编代码·片断:
foo2: pushl %ebp movl %esp, %ebp movl 8(%ebp), %eax popl %ebp movl %eax, %edx sarl $31, %edx xorl %edx, %eax subl %edx, %eax ret .size foo2, .-foo2 .p2align 4,,15 .globl bar .type bar, @function bar: pushl %ebp movl %esp, %ebp movl 8(%ebp), %eax popl %ebp movl %eax, %edx sarl $31, %edx xorl %edx, %eax subl %edx, %eax ret .size bar, .-bar .p2align 4,,15
bar和foo2的代码完全一样,由此可见bar函数自动被内联了。
下面这个测试我用的gcc是4.3
现在我们来去掉static,然后来看会出现什么情况,源码:
long abs2(long x) { return x >= 0 ? x : -x; } long foo2(long a) { return abs(a); } long bar(long a) { return abs2(a); }
来看汇编:
abs2: pushl %ebp movl %esp, %ebp movl 8(%ebp), %eax popl %ebp movl %eax, %edx sarl $31, %edx xorl %edx, %eax subl %edx, %eax ret bar: pushl %ebp movl %esp, %ebp movl 8(%ebp), %eax popl %ebp movl %eax, %edx sarl $31, %edx xorl %edx, %eax subl %edx, %eax ret
可以看到依然会被内联,可是和没有加static相比,会生成abs2这个段.这是因为编译器要做最保守的处理,如果不加static的话,abs2可能还被其他的文件所调用,因此会保留这个abs2.
这里来做个总结吧.实验代码就不贴了,有兴趣可以自己去试试.
1 用O3编译,函数加不加static ,都会被内联.
2 如果函数被调用一次,并且函数体很大
如果为static修饰的函数,他无论如何都会被内联.
如果不加static的话,就需要你强制O3优化了,用O3也会生成abs2那个段.如果O2编译,则会生成跳转指令.
3 如果函数被调用多次.
加不加static修饰的函数,调用次数超过一定的数值都会生成跳转指令.
因此我们一个文件内的内部函数尽量都声明为static的。
接下来来看数组的边界检测,编译器会如何优化:
static char array[100000]; static int write_to(int ofs,char val) { ///检测边界(这个会被优化掉) if (ofs>=0 && ofs<100000) array[ofs]=val; } int main() { int i; for (i=0; i<100000; ++i) array[i]=0;//<这里没有检测边界 for (i=0; i<100000; ++i) write_to(i,-1); }
然后来看对应的汇编代码,其中L6表示第一个赋值,L7为第二个赋值:
.L6: movl $0, array(%eax) addl $4, %eax cmpl $100000, %eax jne .L6 xorl %eax, %eax .p2align 4,,7 .p2align 3 .L7: movl $-1, array(%eax) addl $4, %eax cmpl $100000, %eax ///可以看到边界检测被优化掉了 jne .L7 popl %ebp ret .size main, .-main .p2align 4,,15
可以看到生成了相同的代码,边界检测被编译器优化了(也就是删除掉了).也就是循环的时候(如果我们for循环的边界检测刚好包含本身的边界检测的话,我们不需要多余的边界检测.)如果将for循环的边界检测改为大于100000的话,我们就会看到,编译器会生成相应的边界检测的.
static char array[100000]; static int write_to(int ofs,char val){ if(ofs>=0&&ofs<99999) array[ofs]=val; } int main(){ int i; for(i=0;i<99998;++i)array[i]=0; for(i=1;i<100000;++i)write_to(i,-1); }
汇编代码(只看write_to那部分):
.L6: movb $-1, array(%eax) addl $1, %eax cmpl $100000, %eax je .L4 cmpl $99999, %eax jne .L6 .L4: popl %ecx popl %ebp leal -4(%ecx), %esp
可以看到当for的边界检测包含write_to的边界的时候就会生成这段.
再来看另外的一些边界检测优化:
int regular(int i) { ///这里判断i的边界。 if (i>5 && i<100) return 1; exit(0); }
来看regular的汇编代码:
regular: pushl %ebp movl %esp, %ebp subl $24, %esp movl 8(%ebp), %eax ///先将i减去6 subl $6, %eax ///然后和93比较 cmpl $93, %eax ja .L18 movl $1, %eax leave ret .L18: ///设置返回值为0 movl $0, (%esp) call exit
可以看到i>5 && i<100被优化为 i-6 <93。并且编译器知道exit不会有返回值,因此还会设置正确的返回值。
接下来来看for和while,编译器会有什么不一样的处理:
char array[100000]; int foo5(int a) { int i; for (i=1; i<a; i++) array[i]=array[i-1]+1; } void foo6(int a) { int i =1; while (i<a) { array[i]=array[i-1]+1; i++; } }
来看生成的汇编:
foo5: pushl %ebp movl %esp, %ebp movl 8(%ebp), %ecx pushl %ebx cmpl $1, %ecx jle .L23 movzbl array, %ebx movl $1, %eax foo6: pushl %ebp movl %esp, %ebp movl 8(%ebp), %ecx pushl %ebx cmpl $1, %ecx jle .L29 movzbl array, %ebx movl $1, %eax
可以看到完全一模一样,我记得在老的版本的gcc中,for和while生成的汇编是不一样的。
还有gcc的switch优化为跳转表可以看我前面的blog.
还有一些就是数值计算的优化了,不过这里就不介绍了。
评论
2 楼
lurker0
2010-02-13
》》可以看到i>5 && i<100被优化为 i-6 <93。
如果i为99 左侧条件为真,右侧条件为假,就不等价。
这里是不是有问题?
如果i为99 左侧条件为真,右侧条件为假,就不等价。
这里是不是有问题?
1 楼
seen
2010-02-03
>>可以看到i>5 && i<100被优化为 i-6 <93
从数学上来说是说不通的。
这里有点意思。。。以前不知道还有这一手。。。在CF上玩了点小花样
从数学上来说是说不通的。
这里有点意思。。。以前不知道还有这一手。。。在CF上玩了点小花样
发表评论
-
gdb学习笔记(一)
2009-10-17 14:11 11732这里只是一个摘要。具体的细节还需要去看manual。 1 ... -
ydb的内存模型
2009-09-06 18:02 1978阿宝同学推荐了这个东 ... -
glibc中strlen的实现
2009-08-04 09:10 4526glibc中的strlen的实现主要的思想就是每次检测4个字节 ... -
libevent源码浅析(四)
2009-05-15 23:02 4426最近刚刚一个项目自己用libevent,因此这几天又把libe ... -
libevent源码浅析(三)
2009-03-17 00:08 4583这次我们来看libevent的信号的处理。 在libeven ... -
libevent源码浅析(二)
2009-02-22 00:11 4076我们来看下libevent的定时器的实现 在libevent ... -
libevent源码浅析(一)
2009-02-14 13:23 7433这里分析的是libevent-1.4.9。 PS:前面还看了 ... -
linux下的time处理
2009-01-04 18:02 6843在内核中有3个不同的时间: Wall time(real t ... -
libev简单使用介绍
2008-12-30 09:52 11550更详细的用法请看他的 ... -
linux下的elf结构
2008-12-12 00:20 5186可以看到链接器和加载器看待elf是完全不同的,链接器看到 ... -
php的c扩展
2008-12-07 18:24 4570在php中最核心的一个数据结构就是这个: typedef u ... -
linux下的管理内存相关的函数
2008-11-27 00:56 4484malloc的实现,在linux下的实现是这样的,当所需 ... -
linux下的数据对齐
2008-11-25 12:15 3647数据对齐也就是通过硬件来估算在数据的地址和内存块之间的联系。当 ... -
linux下检测ip冲突
2008-11-16 20:18 8174原理其实很简单,那就是广播一个arp包,然后recv,如果没有 ... -
今天碰到的一个问题
2008-10-29 22:33 1265将位图用 bmptopnm 转成pcl6的打印语言,然后直接c ... -
ftruncate和msync
2008-10-23 22:10 3498int ftruncate(int fd, off_t le ... -
GUN C正则表达式
2008-09-25 23:47 6192最近项目中要处理文本,因此就用了gun的正则表达式,它是pos ... -
看代码看的头晕
2008-09-06 01:04 1868最近工作需要在看ghostscript的代码,看得我头晕眼花, ... -
[转帖]MISRA--作为工业标准的C编程规范
2008-08-21 13:19 2835本文档转贴自孟岩的blog ... -
代码大全读书笔记1
2008-04-26 19:16 3823这么好的书,觉得写点东西,记录一下比较好。 4.1选择编程语 ...
相关推荐
文档虽然没有详细列举所有GCC优化标志,但根据上下文推测,以下几个方面是进行GCC优化时应关注的重点: 1. **编译器警告标志**:使用如`-Wall`、`-Wextra`等标志来开启更多的警告信息,可以帮助开发者发现潜在的...
GCC教程通常会涵盖以下几个关键部分: 1. **安装与配置**:首先,你需要了解如何在不同的操作系统上安装GCC,如在Ubuntu、Windows或MacOS上。这可能涉及到下载源代码、配置编译选项、编译和安装过程。 2. **基本...
安装GCC 4.8.5的步骤通常包括以下几个阶段: 1. **解压**: 首先,你需要使用`tar`命令来解压`gcc.tar.gz`文件。例如,你可以使用`tar -zxvf gcc.tar.gz`命令将其解压到当前目录。 2. **配置**: 解压完成后,进入...
GCC的编译过程分为几个主要阶段:预处理、编译、汇编和链接。预处理阶段处理宏定义、条件编译指令,并生成.i中间文件。编译阶段将预处理后的代码转换为汇编语言,生成.s文件。汇编阶段将汇编代码转换成机器码,形成....
2. **配置**: 进入解压后的目录,运行`./configure`脚本,这是一个自动配置脚本,会检查用户的系统环境并生成适合的Makefile文件。在运行前可能需要设置一些环境变量,例如指定安装路径和依赖库的位置。 3. **编译*...
TDM-GCC的安装通常包括以下几个步骤: 1. 下载并运行"tdm-gcc-10.3.0.exe"。 2. 按照安装向导的指示进行,选择安装路径和组件。 3. 安装完成后,确保将编译器的路径添加到系统的PATH环境变量中,以便在命令行中直接...
在压缩包子文件的文件名称列表中,我们可以看到以下几个组件: 1. `gcc-4.8.5-28.el7.x86_64.rpm`:这是GCC主程序的RPM包,包含了编译器的基本功能,用于将源代码转换为可执行程序。 2. `kernel-headers-3.10.0-...
3. **易于安装**:压缩包中的"Dev-Cpp 5.11 TDM-GCC 4.9.2 Setup.exe"是一个安装程序,只需简单几步就能完成安装,省去了手动配置编译环境的繁琐过程。 4. **集成开发环境**:虽然TDM-GCC本身是一个编译器,但它...
它的工作流程分为几个主要步骤:预处理、编译、汇编和链接。预处理阶段处理#include指令,宏替换和条件编译;编译阶段将预处理后的源代码转换为中间语言——汇编代码;汇编阶段将汇编代码转换为机器码;最后,链接器...
GCC的编译过程通常分为几个步骤: 1. **预处理**:预处理器(cpp)处理源代码中的宏定义、条件编译指令,并处理#include文件,生成.i(或E)文件。 2. **编译**:编译器(cc1或g++)将预处理后的代码转换为汇编语言...
在Linux环境下,C程序的编译通常涉及以下几个步骤: 1. **编译单个源文件**:创建一个简单的C程序,例如经典的"Hello World"程序,然后使用`gcc`命令编译。例如: ```bash gcc -o hello hello.c ``` 这会生成名...
在GCC中,有以下几个关键的知识点: 1. **基本使用**:GCC的基本用法是通过命令行接口调用,例如`gcc -o 输出文件名 源代码文件名`。这将把源代码编译并链接成指定的输出文件。 2. **预处理阶段**:GCC首先进行...
在GCC的优化参数方面,有以下几个常用的选项: - `-O0`:关闭优化,这对于调试代码非常有用。 - `-O1`,`-O2`,`-O3`:分别代表不同程度的优化,`-O3`是最高级别的优化,但可能会增加编译时间。 - `-Os`:优化代码...
3. **配置**:TDM-GCC通常会自动配置好MinGW-w64运行时库,使得编译的程序可以直接在Windows上运行。用户可能需要根据自己的需求调整编译器的配置选项,例如优化级别、警告级别等。 4. **使用**:通过命令行工具...
在GCC 4.8.3中,有以下几个重要的改进和新特性: 1. **C++11/14支持**:GCC 4.8系列开始全面支持C++11标准,并在4.8.3中进一步完善了C++14的一些特性,提升了标准库的实现质量。 2. **性能优化**:GCC 4.8.3包含了...
GCC的发展历程可以概述为几个重要阶段: 1. GCC第一版于1987年发布,标志着自由软件开发领域的重大突破。 2. 1992年GCC推出2.0版本,引入了C++编译能力。 3. 1997年,为了改进优化和对C++的支持,GCC的实验性分支...
通常,编译器的工作过程包括以下几个阶段: 1. **词法分析**:将源代码分解成有意义的符号或“标记”。 2. **语法分析**:将这些标记组织成抽象语法树(AST)。 3. **语义分析**:检查语法树是否符合语言的规则,并...