- 第一章 CPU 简介
- 第二章 Hello,world!
- 第三章 函数开始和结束
- 第四章 栈
- Chapter 5 printf() 与参数处理
- Chapter 6 scanf()
- CHAPER7 访问传递参数
- Chapter 8 一个或者多个字的返回值
- Chapter 9 指针
- Chapter 10 条件跳转
- 第 11 章 选择结构 switch()/case/default
- 第 12 章 循环结构
- 第 13 章 strlen()
- Chapter 14 Division by 9
- chapter 15 用 FPU 工作
- Chapter 16 数组
- Chapter 17 位域
- 第 18 章 结构体
- 19 章 联合体
- 第二十章 函数指针
- 第 21 章 在 32 位环境中的 64 位值
- 第二十二章 SIMD
- 23 章 64 位化
- 24 章 使用 x64 下的 SIMD 来处理浮点数
- 25 章 温度转换
- 26 章 C99 的限制
- 27 章 内联函数
- 第 28 章 得到不正确反汇编结果
- 第 29 章 花指令
- 第 30 章 16 位 Windows
- 第 31 章 类
- 三十二 ostream
- 34.2.2 MSVC
- 34.2.3 C++ 11 std::forward_list
- 34.3 std::vector
- 34.4 std::map and std::set
Chapter 8 一个或者多个字的返回值
X86 架构下通常返回 EAX 寄存器的值,如果是单字节 char,则只使用 EAX 的低 8 位 AL。如果返回 float 类型则使用 FPU 寄存器 ST(0)。ARM 架构下通常返回寄存器 R0。
假如 main() 函数的返回值是 void 而不是 int 会怎么样?
通常启动函数调用 main() 为:
#!bash
push envp
push argv
push argc
call main
push eax
call exit
换句话说为
#!cpp
exit(main(argc,argv,envp));
如果 main() 声明为 void 类型并且函数没有明确返回状态值,通常在 main() 结束时 EAX 寄存器的值被返回,然后作为 exit() 的参数。大多数情况下函数返回的是随机值。这种情况下程序的退出代码为伪随机的。
我们看一个实例,注意 main() 是 void 类型:
#!cpp
#include <stdio.h>
void main()
{
printf ("Hello, world!
");
};
我们在 linux 下编译。
GCC 4.8.1 会使用 puts() 替代 printf()(看前面章节 2.3.3),没有关系,因为 puts() 会返回打印的字符数,就行 printf() 一样。请注意,main() 结束时 EAX 寄存器的值是非 0 的,这意味着 main() 结束时保留 puts() 返回时 EAX 的值。
Listing 8.1: GCC 4.8.1
#!bash
.LC0:
.string "Hello, world!"
main:
push ebp
mov ebp, esp
and esp, -16
sub esp, 16
mov DWORD PTR [esp], OFFSET FLAT:.LC0
call puts
leave
ret
我们写 bash 脚本来看退出状态:
Listing 8.2: tst.sh
#!bash
#!/bin/sh
./hello_world
echo $?
运行:
#!bash
$ tst.sh
Hello, world!
14
14 为打印的字符数。
回到返回值是 EAX 寄存器值的事实,这也就是为什么老的 C 编译器不能够创建返回信息无法拟合到一个寄存器(通常是 int 型)的函数。如果必须这样,应该通过指针来传递。现在可以这样,比如返回整个结构体,这种情况应该避免。如果必须要返回大的结构体,调用者必须开辟存储空间,并通过第一个参数传递指针,整个过程对程序是透明的。像手动通过第一个参数传递指针一样,只是编译器隐藏了这个过程。
小例子:
#!cpp
struct s
{
int a;
int b;
int c;
};
struct s get_some_values (int a)
{
struct s rt;
rt.a=a+1;
rt.b=a+2;
rt.c=a+3;
return rt;
};
…我们可以得到(MSVC 2010 /Ox):
#!bash
$T3853 = 8 ; size = 4
_a$ = 12 ; size = 4
?get_some_values@@YA?AUs@@H@Z PROC ; get_some_values
mov ecx, DWORD PTR _a$[esp-4]
mov eax, DWORD PTR $T3853[esp-4]
lea edx, DWORD PTR [ecx+1]
mov DWORD PTR [eax], edx
lea edx, DWORD PTR [ecx+2]
add ecx, 3
mov DWORD PTR [eax+4], edx
mov DWORD PTR [eax+8], ecx
ret 0
?get_some_values@@YA?AUs@@H@Z ENDP ; get_some_values
内部变量传递指针到结构体的宏为$T3853。
这个例子可以用 C99 语言扩展来重写:
#!bash
struct s
{
int a;
int b;
int c;
};
struct s get_some_values (int a)
{
return (struct s){.a=a+1, .b=a+2, .c=a+3};
};
Listing 8.3: GCC 4.8.1
#!bash
_get_some_values proc near
ptr_to_struct = dword ptr 4
a = dword ptr 8
mov edx, [esp+a]
mov eax, [esp+ptr_to_struct]
lea ecx, [edx+1]
mov [eax], ecx
lea ecx, [edx+2]
add edx, 3
mov [eax+4], ecx
mov [eax+8], edx
retn
_get_some_values endp
我们可以看到,函数仅仅填充调用者申请的结构体空间的相应字段。因此没有性能缺陷。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论