C语言函数返回值解析

一般来说,函数在返回返回值的时候汇编代码一般都会将待返回的值放入eax寄存器暂存,接着再调用mov指令将eax中返回值写入对应的变量中,如下代码:做简单的sum运算,
 1 #include   
  2 #include
  3 
  4 int sum(int ivar1,int ivar2)
  5 {
  6   return ivar1+ivar2;
  7 }
  8 
  9 int main()
 10 {
 11   int res=sum(1,2);
 12 }
接着我们来看对应的汇编代码
5 sum:
  6     pushl   %ebp
  7     movl    %esp, %ebp
  8     movl    12(%ebp), %eax
  9     movl    8(%ebp), %edx
 10     leal    (%edx,%eax), %eax   //将函数返回值暂存在eax寄存器中
 11     popl    %ebp  //main函数栈基地址出栈
 12     ret  //返回到上调用函数,并调整PC寄存器为main程序下一条指令的地址
 13     .size   sum, .-sum
 14 .globl main
 15     .type   main, @function
 16 main:
 17     pushl   %ebp
 18     movl    %esp, %ebp
 19     subl    $24, %esp
 20     movl    $2, 4(%esp)
 21     movl    $1, (%esp)
 22     call    sum
 23     movl    %eax, -4(%ebp)  //sum函数返回的返回值通过eax现在赋值给变量res。
 24     leave
 25     ret
函数在调用结束后不会会自动清理栈上的内存,如果我们再次访问原来栈空间上的数据,那么我们读取到的数据还是可以读取到的,例如我上面sum程序的基础上加个sum_1
 9 int* sum_1(int ivar1,int ivar2)
 10 {
 11   int tmp= ivar1+ivar2;
 12   return &tmp;
 13 
}

那么在编译的时候,gcc报了一个这样的警告:函数返回了一个局部变量的地址,但是这种情况再运行会程序没什么问题。
我们运行程序

结果依然是对的。但是我们不推荐这么做,因为栈是一个进程调用函数的动态区域,现在是这样,以后又是怎么样的我们无从知晓。最好就是返回值,函数内部的局部变量的地址尽量少返回。不过有下面三中情况可以返回指针。

(1)函数中存储了静态局部变量,可以通过返回静态局部变量的地址,来修改静态局部变量的值。如下面函数
14 char *fun_1()
 15 {
 16     static char name[]="jack";
 17     return name;
 18 }
那么局部静态变量是存储在.data段的,不是在堆栈上的,所以函数在返回后,name数组里面的数据jack依旧存在,并且是伴随程序结束消亡的。在这里"jack"这个字符串常量是从.rodata区域复制过来到name这一块内存里面的(言外之意就是说"jack"是存储在.rodata区域)。由于Name数组是在可读可写的.data段里,所以,我们可以对这一段内存的数据进行修改。
所以在主程序里面写入
char *ptr=fun_1();
    *(ptr+1)='i';
是合法的。
(2)函数返回一个const修饰的常量,也可以通过返回它地址在主函数中访问。原来在于:const修饰的常量存储在.rodata段,跟函数栈上面的数据关系,所以在也可以返回指针。
看下面
19 char *fun_2()
 20 {
 21     char *p="alice";
 22     return p;
 23 }
主函数还是
char *ptr=fun_1();
*(ptr+1)='i';
编译并没有报错。但是我们允许起来,发现。。
崩溃了!原因在于:对于.rodata区域里的”alice“进行修改。看汇编代码。
23 .section .rodata
24 .LC0:
25 .string "alice"
26 .text

49 call fun_2
50 movl %eax, 28(%esp)
51 movl 28(%esp), %eax
52 addl $1, %eax
53 movb $105, (%eax) //试图将'i'写入“alice“
使用gdb调试
C语言函数返回值解析_第1张图片
报出SIGSEGV段访问错误。说明无权限访问。
再回到主题看
(3)函数返回一个指向动态内存分配块的指针。这个是最常见的了。因为我们都知道动态分配内存是malloc函数在堆上分配size个字节的内存,并返回一个(void *)类型的指针,因此我们在返回时需要强转为目标类型。由于动态内存时在堆上进行分配的因此需要程序员手动释放内存,否则会发生内存泄漏!
先下面程序题(你会对此印象深刻!)
void getMemory(char *p)
{
p=(char *)malloc(100);
}
main()
{
char *str=NULL;
getMemory(str);
strcpy(str,NULL);
printf("%s",str);
}
会打印什么?猜一猜!
哈哈,内存崩溃了!
原因在于:getMemory里面的p是保存在栈上的局部变量,是str的一个副本。函数展开后p对应的栈空间存储着已分配内存的地址,现在函数结束了,操作系统收回函数的栈空间,原来的str还是原来的NULL,现在我们将一个“hello”字符串写到0地址里面当然就会发生错误了,因为0地址这个区域是用户不可访问的。我们原意是将hello字符串写入动态分配的内存里,现在写到了0地址内存单元里面,原因在于getMemory没有返回值啊,因此正确的程序应该写成一下
  5 char * getMemory(char *p)
  6 {
  7   p=(char*)malloc(100);
  8   assert(p!=NULL);
  9   return p;
 10 }
 11 
 12 int main()
 13 {
 14   char *ptr=NULL;
 15   ptr=getMemory(ptr);
 16   strcat(ptr,"hello");
 17   printf("%s\n",ptr);
 18   free(ptr);
 19 }
印象是不是很深刻啦!因此,在函数中申请的动态内存空间对应的指针一定要返回!并在主程序中释放。







你可能感兴趣的:(C语言)