附加作业-堆和栈

本次作业要探讨堆和栈的性质,为了严格起见,先说明下我这里的软件环境:

操作系统:OS X 10.9

编译器:CLANG & LLVM 3.3

 

先说内存占用部分:

由于我的编程能力弱爆了,只会写最简单的程序,并且各种规范都没有注意。于是我就写了如下程序来考察栈对于内存的使用:

 

#include <stdlib.h>
void foo()
{
  int a, b;
  a = 1;
  b = a;
}

int main()
{
  int i;
  while (1) {
    foo();
  }
  return 0;
}

 

这段程序就在做一个很无聊的事情,不断申请空间。。。但是由于栈会自动回退,所以内存占用比较良好:

然后弱爆了的我为了测试堆的内存占用,写出了如下代码:

#include <stdlib.h>
int foo()
{
  int a, *b;
  a = 1;
  b = malloc(sizeof(int));
  *b = a;
  return *b;
}

int main()
{
  int i;
  while (1) {
    foo();
  }
  return 0;
}

有一点需要注意,这里返回值是传的值,不是指针,所以输出的结果也是正确的。可是。。。:

刚开没多久,就占了3.29G的内存。由于比较吓人,我就把这个程序关了。我测试过一次用了5.9G,后来怕电脑爆内存,就干掉进程了(爆内存意味着比较彻底的死机)。

后来我又测试了一下,这次在编译的时候,我用开了编译器的优化功能

gcc -O2 heapMemoryOccupy.c

这次执行的时候内存就很合理了:

这个现象引起了我的好奇,果断从汇编入手(*nix和win有objdump,mac是otool),以下为不加优化的程序:

heapMemoryOccupy.o:
(__TEXT,__text) section
_foo:
0000000000000000    pushq    %rbp
0000000000000001    movq    %rsp, %rbp
0000000000000004    subq    $0x10, %rsp
0000000000000008    movabsq    $0x4, %rdi
0000000000000012    movl    $0x1, 0xfffffffffffffffc(%rbp)
0000000000000019    callq    0x1e
000000000000001e    movq    %rax, 0xfffffffffffffff0(%rbp)
0000000000000022    movl    0xfffffffffffffffc(%rbp), %ecx
0000000000000025    movq    0xfffffffffffffff0(%rbp), %rax
0000000000000029    movl    %ecx, (%rax)
000000000000002b    movq    0xfffffffffffffff0(%rbp), %rax
000000000000002f    movl    (%rax), %eax
0000000000000031    addq    $0x10, %rsp
0000000000000035    popq    %rbp
0000000000000036    ret
0000000000000037    nopw    (%rax,%rax)
_main:
0000000000000040    pushq    %rbp
0000000000000041    movq    %rsp, %rbp
0000000000000044    subq    $0x10, %rsp
0000000000000048    movl    $0x0, 0xfffffffffffffffc(%rbp)
000000000000004f    callq    0x54
0000000000000054    movl    %eax, 0xfffffffffffffff4(%rbp)
0000000000000057    jmpq    0x4f

以下为加了优化的程序:

heapMemoryOccupy.o:
(__TEXT,__text) section
_foo:
0000000000000000    pushq    %rbp
0000000000000001    movq    %rsp, %rbp
0000000000000004    movl    $0x1, %eax
0000000000000009    popq    %rbp
000000000000000a    ret
000000000000000b    nopl    (%rax,%rax)
_main:
0000000000000010    pushq    %rbp
0000000000000011    movq    %rsp, %rbp
0000000000000014    nopw    %cs:(%rax,%rax)
0000000000000020    jmp    0x20

编译器优化的原则是:在不改变结果的情况下尽可能给程序提速。不过这个结果确实令我感到震惊,直接根据语义,把malloc函数给优化掉了。看来我们的编译原理课上讲的东西还是太弱了。

 

关于运行速度:

由于某种机缘巧合,我的编程能力突然提高了,注意到了可能产生的内存泄漏,于是写出了下面两端代码,测试在保证正确性情况下堆和栈的速度:

先是栈:

#include <stdlib.h>
void foo()
{
  int a, b;
  a = 1;
  b = a;
}

int main()
{
  long i = 1e9;
  while ((i--)>0) {
    foo();
  }
  return 0;
}

运行速度:

 

然后是堆,代码:

#include <stdlib.h>
void foo()
{
  int a, *b;
  a = 1;
  b = malloc(sizeof(int));
  *b = a;
  free(b);
}

int main()
{
  int i = 1e9;
  while ((i--) > 0) {
    foo();
  }
  return 0;
}

下面是速度:(测试的时候还一度怀疑能不能出结果了)

由此可见,由于申请和释放的开销,局部变量还是用栈比较好。

 

栈的不足之处,传说中的栈溢出(stack overflow):

由于蛋疼,我写了如下代码:

#include <stdio.h>
void foo(int a)
{
  printf("%d\n", a);
  foo(a+1);
}
int main()
{
  foo(1);
  return 0;
}

这个就是一个无限递归,然后我们看它的结果:

由此可见,只要262008个最简单的递归,栈就溢出了,由此可以算出栈大约就是几兆的空间。

也由此告诉我们,由于各种限制,尽量少用递归比较好。不过,我又发现,开了优化参数以后,由于代码的优化,跑了很久都没栈溢出。。。

 

 

堆的另一个问题:指针悬挂

内存泄漏是众所周知,我们的第二段代码就是一个典型的内存泄漏,从截图可以看出,泄漏了将近4G内存(囧TL)。下面我来演示以下指针悬挂:

#include <stdlib.h>
#include <stdio.h>
int main()
{
  int *i, *j;
  float *f;
  i = malloc(sizeof(int));
  *i = 1;
  j = i;
  printf("*j=%d\n", *j);
  free(i);
  f = malloc(sizeof(float));
  *f = 0.5;
  printf("*j=%d\n", *j);
  return 0;
}

这里的亮点在于,j的位置后来变成了一个浮点数,以下是运行结果:

这是mac系统下的结果,在别的系统就不能保证了。由此得出的结论也很简单,这是要避免的情况。

 

 

指针是c语言的一大迷人的地方,指针用的犀利,程序也就很犀利。指针用萎了程序也就萎了。以上总结的都是我血泪教训,分享给大家,希望能够共同进步。

 

posted on 2013-12-09 01:10  不想嚣张  阅读(513)  评论(0编辑  收藏  举报

导航