C语言一些特性的分析

这篇文章主要是讲解了一些c语言里边容易让人迷惑的方面,通过分析程序的汇编代码来解释.这里边的东西在<C专家编程>这本书里边讲的很清楚的

在这里我主要是想用汇编代码进行一下说明,看看到底是怎么回事.

 


编译器gcc 有的代码进行了2级的优化,当然不进形优化会看的更清除gcc -S -O2

 


BY YANGXI

 

 

 

 


1.让我们来看看数组和指针的相同和不同的地方.我们首先应该明白一个基本的概念:对于x=y这个表达式,符号x代表一个地址(在这里叫做左值.)而符号y在这里代表一个地址里边的内容(在这里叫做右值).我们看下边这断code.

void a()

{

int *p

int a[10];

p = a;

p[2] = 2;

a[3] = 3;

}

下面是a函数的汇编代码

a:

pushl %ebp

movl %esp, %ebp

subl $72, %esp ;这里给p和a[10]分配空间

leal -72(%ebp), %eax ;将a数组的第一个元素的地址传递给p,(p的地址是%ebp-12,然后p的地址的内容是a数组第一个元素的地址)

movl %eax, -12(%ebp)

movl -12(%ebp), %eax

addl $8, %eax

movl $2, (%eax);现在eax里面放的就是a第3个元素的地址

movl $3, -60(%ebp)

leave

ret

在上面的例子里我们看到对p[2]的寻址过程是

 


1,首先得到p的地址.

2,得到p地址中的内容(a数组第一个数组的地址).

3,*p + sizeof(int)*2的计算得到数组a的第三个元素的地址.

4,向这个地址写数据.

 


但是对于a[3]的寻址却是 ebp-72 + sizeof(int)*3 计算得到ebp - 60比指针少一个从指针的地址取

数据的过程,这就是指针和数组的一个不同点.我们看下边的例子.

int p[3];

void main()

{

extern int *p;

p[2] = 4;

}

当编译这个程序的时候产生错误conflicting types for p .我们来假设能够运行会产生什么惊人的效果.

由于p在main中被声明为指针所以p[2]会按照指针的方式进行寻址

1,首先得到p的地址(假设为 a).

2,得到a中的内容(假设是b)作为数组的第一个元素的地址(b其实就是数组第一个元素在这里却被当成了数组中第一个元素的地址)由于这一步错了所以后边也就错了,最后4被写到了一个错误的地址中去.这就是为什么我们对于指针和数组的声明和定义的形式必需相同的原因了.但是为什么我们在函数中定义了int *p int a[10] 却可以在表达式和函数参数传递的时候混用呢,这是因为ansic规定

在表达试和参数传递中把数组名a作为指向第一个元素的指针,所以下边的代码就没有问题了.

void a(int *p)

{

p[0] = 1;

}

void main()

{

int p[2];

a(p);

*p = 2;

}

我们来看一下它的汇编代码

a:

pushl %ebp

movl %esp, %ebp

movl 8(%ebp), %eax

movl $1, (%eax)

leave

ret

main:

pushl %ebp

movl %esp, %ebp

subl $8, %esp

andl $-16, %esp

leal -8(%ebp), %eax ;ebp - 8就是我们的数组p第一个元素的地址了

subl $12, %esp

pushl %eax ;这里就是我们传递给a函数的p(在这里编译器把它转换成了指针)

call a

popl %eax

popl %edx

movl $2, -8(%ebp)

 

 

 

 

 

 

2,我们在写程序的时候经常这样来定义一个字符常量char *p = "hello world"如果这个时候想p[2] = 'C'就会产生错误,这又是为什么呢,我们看下边的代码.

void

main()

{

char *p = "hello world";

p[0] = 'a';

}

这个程序在运行的时候会产生段错误.为什么呢,我们来看一下它的汇编代码

.section .rodata.str1.1,"aMS",@progbits,1

.LC0:

.string "hello world";原来他们在定义的时候被放到了.rodata这个只读的段里面,所以我们在往这个地址些数据的时候产生了段错误

.text

.p2align 2,,3

.globl main

.type main, @function

main:

pushl %ebp

movl %esp, %ebp

subl $8, %esp

andl $-16, %esp

movb $97, .LC0

leave

ret

那我们下边的代码会产生什么后果呢

void

main()

{

int *p = {1,2,3};

p[0] = 8;

}

又产生了段错误不过这次产生段错误的原因可就大不一样了,让我们看看到底是怎么回事把

ain:

pushl %ebp

movl %esp, %ebp

subl $8, %esp

movl $8, 1 ;出现了一个1是不是感到很好奇,其实这就是P[0]= 8 让我们来分析一下.首先在初始话的时候p的地址的内容被初始化成了

1(在这里只给p分配了一个四个节的空间放p的内容,在这里会被付值成1.然而我们想让p所指向的数据却没有分配空间,而且他们也没有被放到.rodata中去,真可怜,看来只有char *能享受.radata VIP级别的待遇了)还记得我们前边说的指针怎么寻址了马,得到p的地址的内容就是数组的第一个元素的地址,p的地址的内容就是1所以会有这个1的出现,这个程序访问了不该访问的地址,它死定了,导致了段错误

andl $-16, %esp

leave

ret

 

 

 

 

 

 

3,p++ ++p的区别,这个估计大家都知道把,那我们来看看它到底为什么不一样,我们来看下边的代码

 


void main()

{

int a,b,c;


a = 1;

b = a++;

c = ++a;

}

我们来看一下汇编代码

main:

pushl %ebp

movl %esp, %ebp

subl $24, %esp

andl $-16, %esp

movl $0, %eax

subl %eax, %esp

movl $1, -4(%ebp) ;ebp - 4 里边放的是a. 这行是a = 1

movl -4(%ebp), %edx;把a的内容放到了edx其实就是b

movl %edx,-8(%ebp) ;这里马上就把b些到了ebp-8的地址里边也就是也到了b的地址里边所以 b也是1

leal -4(%ebp), %eax

incl (%eax);等付值完了以后在对a的内容进行加操作

 

 

 

leal -4(%ebp), %eax

incl (%eax);这里和上边正相反,这里是先对地址中的内容进行了加的操作然后才把它放到寄存器中去进行c = a 的付值运算

movl -4(%ebp), %eax

movl %eax, -12(%ebp)

leave

ret

上边的代码很清楚的表达了他们之间的区别b = a++ 是先方到寄存器中去参加运算然后改变存储器中的value c= ++a却是相反的.

 

 

 

这里就举这几个很简单的例子,其实还有很多这样的例子,比如说为什么函数不能返回在函数内部定义的数组等等,我的目的是抛砖引玉.如果对这个感兴趣完全可以自己来分析.我认为语言其实就是对机器的

一层一层的抽象,语言的目的不同,思想不同抽象的程度和方法就不同.但他们最后都要变成汇编代码,然后用链接器生成可执行文件.突然间想起了

the tao of programming 里边的话

 


The Tao gave birth to machine language. Machine language gave birth to the assembler.

道生机器语言,机器语言生汇编嚣。

 


The assembler gave birth to the compiler. Now there are ten thousand languages.

汇编器生编译器,最后产生上万种高级语言。

 


Each language has its purpose, however humble. Each language expresses the Yin and Yang of software.

Each language has its place within the Tao.

不论多么的微不足道,每种语言都有它自己的目的,每种语言都表达了软件的阴阳两极。每种语言都各得其道.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值