汇编告诉你为什么c++可以对函数重载

这是这个blog的第一篇博文,先说说点别的吧。


最近闲得没事,想搞搞自己的编程语言,暂时命名为Fun吧,希望最终的结果是这东西能用,大家觉得有趣,就可以了。虽然不一定能成功,但做到多少是多少吧。过程中学到的才是关键。

目标是要把语言最终翻译到native code。因为总不喜欢有虚拟机的语言,虽然平时用着也很爽。喜欢c的高效,但是也喜欢c#、python之类的表达能力强的语言。貌似目前能翻译到native code的语言,表达能力是一个问题,至少没有一些高级特性的支持,编码量就一下子上去了。总是在考虑how to do,而不是what to do。

然后就想,能不能搞个试验,看能否把这几个结合起来,于是就有了上述的想法。先从linux开始吧,毕竟资料比较多,以后如果看着还有点用途,有人加入的话,才照顾windows吧。

综合衡量之后,大体方向是翻译到汇编语言(at&t语法,可以用as来编译),对接glibc的部分功能形成core。毕竟用汇编system call封api,linus、gnu那帮大牛早就做了。在这点上花时间不大值得。

只是兴趣,不喜欢的请绕路,别乱喷。


既然要用到glibc,必须在汇编层面把需要的东西都摸得一清二楚。


在本系列的文章中,我将比较深入地介绍有关汇编、c\c++、glibc之类的知识。也不能说 很系统全面,在做笔记之余,跟大家分享,希望对大家有点用途。

我将使用gnu tool chain系列的工具。

c\c++编译器为gcc,连接器为ld,汇编器为as

因为as虽然是用at&t语法,但是支持的cpu以及instruction都比较多,而且还有各种优化选项,比nasm强大点。毕竟是linux里头的国家队。哈哈。

系统为linux(本人有系统洁癖,同时是个更新控,使用archlinux,需要啥才装啥。卖个广告吧,速度的确非常快。之前贪新鲜装了ubuntu11.10,对那个unity界面十分不爽,比gnome-shell差多了,而且漂亮是漂亮了,但是响应速度跟以前相比,降低的不是一个数量级呀。受不了,就折腾了一个星期东google,西baidu,终于装上archlinux以及配置好gnome-shell了)


当然,在windows平台上,结果也应该是一样的。有兴趣的可以使用mingw试试。


废话说多了,开始正题吧。这篇文章结构如下

1、比较c,c++对于类似的函数,翻译到汇编语言里头的区别

2、为什么这么多project,特别是库,要用C,而不用C++【这里纯属个人体会】


先看下面一段C函数  (文件名是1.c)

#include <stdio.h>

int func( int a ) {
        return a + 1;
}

int main() {
        printf( "%d\n", func( 1 ) );
        return 0;
}

非常简单。哈。使用下面命令把这段代码翻译到汇编,不翻译到二进制,不链接。

$ cc -S 1.c

$ ls
1.c  1.s

其中1.s的内容如下

	.file	"1.c"
	.text
	.globl	func
	.type	func, @function
func:
.LFB0:
	.cfi_startproc
	pushl	%ebp
	.cfi_def_cfa_offset 8
	.cfi_offset 5, -8
	movl	%esp, %ebp
	.cfi_def_cfa_register 5
	movl	8(%ebp), %eax
	addl	$1, %eax
	popl	%ebp
	.cfi_def_cfa 4, 4
	.cfi_restore 5
	ret
	.cfi_endproc
.LFE0:
	.size	func, .-func
	.section	.rodata
.LC0:
	.string	"%d\n"
	.text
	.globl	main
	.type	main, @function
main:
.LFB1:
	.cfi_startproc
	pushl	%ebp
	.cfi_def_cfa_offset 8
	.cfi_offset 5, -8
	movl	%esp, %ebp
	.cfi_def_cfa_register 5
	andl	$-16, %esp
	subl	$16, %esp
	movl	$1, (%esp)
	call	func
	movl	$.LC0, %edx
	movl	%eax, 4(%esp)
	movl	%edx, (%esp)
	call	printf
	movl	$0, %eax
	leave
	.cfi_restore 5
	.cfi_def_cfa 4, 4
	ret
	.cfi_endproc
.LFE1:
	.size	main, .-main
	.ident	"GCC: (GNU) 4.6.2"
	.section	.note.GNU-stack,"",@progbits

而对于下面一段C++代码(p1.cpp)

#include <stdio.h>

int func( int a ) {
	return a + 1;
}

int func( double a ) {
	return a + 1;
}

int main() {
	printf( "%d\n", func( 1 ) );
	printf( "%f\n", func( 1.0 ) );
	return 0;
}

同样使用 g++ -S p1.cpp之后,得到的p1.s如下

	.file	"p1.cpp"
	.text
	.globl	_Z4funci
	.type	_Z4funci, @function
_Z4funci:
.LFB0:
	.cfi_startproc
	pushl	%ebp
	.cfi_def_cfa_offset 8
	.cfi_offset 5, -8
	movl	%esp, %ebp
	.cfi_def_cfa_register 5
	movl	8(%ebp), %eax
	addl	$1, %eax
	popl	%ebp
	.cfi_def_cfa 4, 4
	.cfi_restore 5
	ret
	.cfi_endproc
.LFE0:
	.size	_Z4funci, .-_Z4funci
	.globl	_Z4funcd
	.type	_Z4funcd, @function
_Z4funcd:
.LFB1:
	.cfi_startproc
	pushl	%ebp
	.cfi_def_cfa_offset 8
	.cfi_offset 5, -8
	movl	%esp, %ebp
	.cfi_def_cfa_register 5
	subl	$16, %esp
	movl	8(%ebp), %eax
	movl	%eax, -8(%ebp)
	movl	12(%ebp), %eax
	movl	%eax, -4(%ebp)
	fldl	-8(%ebp)
	fld1
	faddp	%st, %st(1)
	fnstcw	-10(%ebp)
	movzwl	-10(%ebp), %eax
	movb	$12, %ah
	movw	%ax, -12(%ebp)
	fldcw	-12(%ebp)
	fistpl	-16(%ebp)
	fldcw	-10(%ebp)
	movl	-16(%ebp), %eax
	leave
	.cfi_restore 5
	.cfi_def_cfa 4, 4
	ret
	.cfi_endproc
.LFE1:
	.size	_Z4funcd, .-_Z4funcd
	.section	.rodata
.LC2:
	.string	"%d\n"
.LC3:
	.string	"%f\n"
	.text
	.globl	main
	.type	main, @function
main:
.LFB2:
	.cfi_startproc
	pushl	%ebp
	.cfi_def_cfa_offset 8
	.cfi_offset 5, -8
	movl	%esp, %ebp
	.cfi_def_cfa_register 5
	andl	$-16, %esp
	subl	$16, %esp
	movl	$1, (%esp)
	call	_Z4funci
	movl	%eax, 4(%esp)
	movl	$.LC2, (%esp)
	call	printf
	fld1
	fstpl	(%esp)
	call	_Z4funcd
	movl	%eax, 4(%esp)
	movl	$.LC3, (%esp)
	call	printf
	movl	$0, %eax
	leave
	.cfi_restore 5
	.cfi_def_cfa 4, 4
	ret
	.cfi_endproc
.LFE2:
	.size	main, .-main
	.ident	"GCC: (GNU) 4.6.2 20111125 (prerelease)"
	.section	.note.GNU-stack,"",@progbits

为了方便比较,我特地把两段汇编代码标记红色的部分放到一起

1.c

	.globl	func
	.type	func, @function
func:


p1.cpp

	.globl	_Z4funci
	.type	_Z4funci, @function
_Z4funci:
	.globl	_Z4funcd
	.type	_Z4funcd, @function
_Z4funcd:

看到没,同样是函数名func,c翻译过来,汇编的label是func,c++翻译过来,就变了样了,前面加了点_Z4,后面一个加了i,一个加了d(仅仅在这个例子里头)。

为什么c++可以对函数进行重载,原因就在这里,因为在汇编里头,这根本就是2个函数,label不一样,也就是名字不一样,地址也不一样。

c++翻译到汇编的时候,真正的函数名是跟参数类型相关的,虽然在c++源代码里头,函数名一样,但在汇编里头,label跟参数相关,通俗点说,在汇编之后函数名不一样。


如果要对这两个cc汇编出来的进行编译,用as是不行的,用cc才行。

cc要求的的entry point是main

as要求的entry point是_start


到这里,大家应该知道为什么很多库要用C写了吧?

个人觉得,对于很多二次开发,特别是在嵌入式里头,真正有用的就是编译好的library,什么头文件呀,源代码呀之类的都不是。例如,我要基于glibc开发自己的语言,做library到其他语言的binding的(例如gtk到python、c++、java等语言的绑定),头文件对于我来说有用途吗?除了看看函数名,看看参数类型,其他用处真的不大。对于一个函数,如果用c写,在编译好的二进制里头,经过反汇编,很容易就可以找到具体的位置。或者经过反汇编,可以知道在汇编层面的调用方法,如果是用c++写的,一编译,整个函数名都面目全非了,要找到那个位置就难了。


估计linus老人家说C++是最恶心的语言,其中一个原因就是这个吧。




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值