关闭

用C学习内存

标签: c语言内存编译
3826人阅读 评论(0) 收藏 举报
分类:

内存分段

32位操作系统,地址总线是32位,寻址空间就是32位,内存编号只能编到32个二进制位,故其只能使用4G内存。

空间:

232byte=210×210×210×22byte=1024×1024×1024×4byte=1024×1024×4K=1024×4M=4G

1 byte = 8 bit, 32bit对应4字节。
64位操作系统对应的内存就是 264byte

内存分段 用途
系统内存 操作系统的内核使用
记录函数执行,存储函数中的变量
可分配内存 给应用程序动态分配
malloc获取的内存
数据段 存储全局变量和常量
代码段 存储编译后的二进制数据

上表排列是有规律的,代码段在内存的低位段0x0,内核位于高位段0xffff..f
变量代表着某块内存存储着特定的数据。

栈内变量的内存地址:

#include <stdio.h>
#include <stdlib.h>
typedef unsigned long long UINT64;
int main(void) {
    int a,b;
    printf("&a: %llu, &b: %llu\n",(UINT64)&a,(UINT64)&b);
    int *p = &a;
    int *p1 = &a;
    int c;
    printf("&c: %llu\n",(UINT64)&c);
    return EXIT_SUCCESS;
}

使用gdb()调试:

$ gcc -g -o exe memory_learn.c 
$ gdb exe
(gdb) start
Temporary breakpoint 1, main () at memory_learn.c:14
14  int main(void) {
(gdb) n
16      printf("&a: %llu, &b: %llu\n",(UINT64)&a,(UINT64)&b);
(gdb) p &a
$1 = (int *) 0x7fffffffdc9c
(gdb) p &b
$2 = (int *) 0x7fffffffdca0
(gdb) n
&a: 140737488346268, &b: 140737488346272
17      int *p = &a;
(gdb) n
18      int *p1 = &a;
(gdb) p &p
$3 = (int **) 0x7fffffffdca8
(gdb) n
20      printf("&c: %llu\n",(UINT64)&c);
(gdb) p &p1
$4 = (int **) 0x7fffffffdcb0
(gdb) n
&c: 140737488346276
21      return EXIT_SUCCESS;

从上面的信息可以发现,先后声明的a,b在内存中是连续的,sizeof(int)等于4,正好是地址差,同时指针p指向a,且p本身有自己的地址,是0x7fffffffdca8. 和&b相差4的是&c,不是&p,这是编译器的优化处理。编译器会将同一类型的变量的声明放在一起。这种优化使得程序的效率更高。
在32位操作系统中,指针需要4个字节存储,在64位操作系统中就需要8个字节来存储了。本系统的信息:

$ uname -a
Linux ubuntu 4.2.0-42-generic #49-Ubuntu SMP Tue Jun 28 21:26:26 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux

所以&p1和&p的差是8
同时,我们发现上面的所有的地址都和0xff..f很相近,所以main()是放在栈内存中的。

再看一个栈和全局变量的例子:

/*
 ============================================================================
 Name        : memory_learn.c
 Author      : theArcticOcean
 Version     :
 Copyright   :
 Description : C, Ansi-style
 ============================================================================
 */

#include <stdio.h>
#include <stdlib.h>
int global;
int sum(int a,int b){
    return a+b;
}
int square(int a){
    return a*a;
}
int square_sum(int a,int b){
    int aa = square(a);
    int bb = square(b);
    int ans = sum(aa,bb);
    return ans;
}
int main(void) {
    int a,b;
    a=3;
    b=6;
    printf("square_sum is %d\n",square_sum(a,b));
    printf("global is %d\n",global);
    return EXIT_SUCCESS;
}
(gdb) start
Temporary breakpoint 1 at 0x4005a1: file memory_learn.c, line 28.
Starting program: /home/edemon/workspace/memory_learn/src/exe 

Temporary breakpoint 1, main () at memory_learn.c:28
28      a=3;
(gdb) n
29      b=6;
(gdb) n
30      printf("square_sum is %d\n",square_sum(a,b));
(gdb) s
square_sum (a=3, b=6) at memory_learn.c:21
21      int aa = square(a);
(gdb) s
square (a=3) at memory_learn.c:18
18      return a*a;
(gdb) bt
#0  square (a=3) at memory_learn.c:18
#1  0x0000000000400572 in square_sum (a=3, b=6) at memory_learn.c:21
#2  0x00000000004005be in main () at memory_learn.c:30
(gdb) n
19  }
(gdb) n
square_sum (a=3, b=6) at memory_learn.c:22
22      int bb = square(b);
(gdb) n
square_sum (a=3, b=6) at memory_learn.c:23
23      int ans = sum(aa,bb);
(gdb) s
sum (a=9, b=36) at memory_learn.c:15
15      return a+b;
(gdb) bt
#0  sum (a=9, b=36) at memory_learn.c:15
#1  0x0000000000400591 in square_sum (a=3, b=6) at memory_learn.c:23
#2  0x00000000004005be in main () at memory_learn.c:30
...
(gdb) p &global
$1 = (int *) 0x601044 <global>
(gdb) p &a
$2 = (int *) 0x7fffffffdcb8
(gdb) p sum
$3 = {int (int, int)} 0x400536 <sum>
(gdb) p &sum
$4 = (int (*)(int, int)) 0x400536 <sum>
(gdb) p main
$5 = {int (void)} 0x400599 <main>

bt命令让我们从函数的地址大小关系能看到函数调用关系,先被调用者地址越大, 那些函数是在代码段中。
函数中的变量保存在栈中,全局变量global保存在数据段中,所以&a明显比&global大。

数组的内存分配

多维数组的元素的内存地址是否是简单的线性分布?让我们测试看看。

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int a[3][5];
    int i,j;
    for(i=0;i<3;i++){
        for(j=0;j<5;j++){
            printf("%p ",&a[i][j]);
        }
        puts("");
    }
    return EXIT_SUCCESS;
}
/*
0x7fff35512b40 0x7fff35512b44 0x7fff35512b48 0x7fff35512b4c 0x7fff35512b50 
0x7fff35512b54 0x7fff35512b58 0x7fff35512b5c 0x7fff35512b60 0x7fff35512b64 
0x7fff35512b68 0x7fff35512b6c 0x7fff35512b70 0x7fff35512b74 0x7fff35512b78 
*/

恩,栈中数组地址分布是线性的。指针运算(指针偏移,地址移动)同样证明了这一点

指针偏移

/*
 ============================================================================
 Name        : array_mem.c
 Author      : theArcticOcean
 Version     :
 Copyright   :
 Description : C, Ansi-style
 ============================================================================
 */

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int a[3][5];
    int *p[3] = {NULL};
    int i,j;
    for(i=0;i<3;i++){
        p[i] = a[i];
    }
    for(i=0;i<3;i++){
        for(j=0;j<5;j++){
            printf("%9d ",a[i][j]);
        }
        puts("");
    }
    for(i=0;i<3;i++){
        for(j=0;j<5;j++){
            printf("%9d ",*p[i]);
            p[i]++;
        }
        puts("");
    }
    return EXIT_SUCCESS;
}
970839232     32559         0         0         1 
        0   4196237         0   4195584         0 
        0         0   4196160         0   4195584 
970839232     32559         0         0         1 
        0   4196237         0   4195584         0 
        0         0   4196160         0   4195584 

更加简单的指针偏移:

/*
 ============================================================================
 Name        : pointer.c
 Author      : theArcticOcean
 Version     :
 Copyright   :
 Description : C, Ansi-style
 ============================================================================
 */

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int a[21];
    int *p = a;
    int i;
    for(i=0;i<21;i++){
        a[i] = i;
    }
    printf("3的倍数:\n");
    p+=3;
    for(i=3;i<21;i+=3){
        printf("%d ",*p);
        p+=3;
    }
    return EXIT_SUCCESS;
}
/*
3的倍数:
3 6 9 12 15 18 
*/

使用gdb调试的结果,因为又一次运行,所以a[][]的内容会不同。

# a的内容
(gdb) p a   
$3 = {{-134253376, 32767, 0, 0, 1}, {0, 4196237, 0, 4195584, 0}, {0, 0, 
    4196160, 0, 4195584}}
#p的地址信息
(gdb) p p
$4 = {0x7fffffffdc80, 0x7fffffffdc94, 0x7fffffffdca8}
#打印0x7fffffffdc80后的5个值
(gdb) x/5d 0x7fffffffdc80
0x7fffffffdc80: -134253376  32767   0   0
0x7fffffffdc90: 1
#打印0x7fffffffdc80后的15个值,也即是所有的数组元素。
(gdb) x/15d 0x7fffffffdc80
0x7fffffffdc80: -134253376  32767   0   0
0x7fffffffdc90: 1   0   4196237 0
0x7fffffffdca0: 4195584 0   0   0
0x7fffffffdcb0: 4196160 0   4195584

看出来,地址相差16字节的区域存储了4个int (sizeof(int) = 4)。

那么堆中又是怎样的呢?

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int a[3][5];
    int *p[3] = {NULL};
    //p = (int *)malloc(sizeof(a));
    /* 当分配一个多维数组空间时,除了第一维外 ,其余各维都必须显式地指定为正常数. 编译器只能记住一维 */
    int i,j;
    for(i=0;i<3;i++){
        p[i] = (int *)malloc(sizeof(a[i]));
    }
    for(i=0;i<3;i++){
        for(j=0;j<5;j++){
            printf("%p ",&p[i][j]);
        }
        puts("");
    }
    return EXIT_SUCCESS;
}
0x1c57010 0x1c57014 0x1c57018 0x1c5701c 0x1c57020 
0x1c57030 0x1c57034 0x1c57038 0x1c5703c 0x1c57040 
0x1c57050 0x1c57054 0x1c57058 0x1c5705c 0x1c57060 

因为存在多次的malloc,所以内存几乎不会线性连续。不过,如果让p[0]指向分配好的二维数组的内存,然后进行指针偏移,可以做到连续:

int main(void) {
    int *p[3] = {NULL};
    /* 当分配一个多维数组空间时,除了第一维外 ,其余各维都必须显式地指定为正常数. 编译器只能记住一维 */
    int i,j;
    p[0] = (int *)malloc(sizeof(int)*15);
    for(i=1;i<3;i++){
        p[i] = p[i-1] + 5;
    }
    for(i=0;i<3;i++){
        for(j=0;j<5;j++){
            printf("%p ",&p[i][j]);
        }
        puts("");
    }
    return EXIT_SUCCESS;
}
0x81d010 0x81d014 0x81d018 0x81d01c 0x81d020 
0x81d024 0x81d028 0x81d02c 0x81d030 0x81d034 
0x81d038 0x81d03c 0x81d040 0x81d044 0x81d048 

字符数组与指针

int main(void) {
    char *p = "hello";
    return EXIT_SUCCESS;
}
(gdb) p p
$1 = 0x400594 "hello"
#查看数组的内容
(gdb) x/6c 0x400594
0x400594:   104 'h' 101 'e' 108 'l' 108 'l' 111 'o' 0 '\000'
#查看多余的内容
(gdb) x/8c 0x400594
0x400594:   104 'h' 101 'e' 108 'l' 108 'l' 111 'o' 0 '\000'    0 '\000' 0 '\000'

为什么不能给字符指针scanf()?

int main(void) {
    char *p ;
    scanf("%s",p);
    printf("%s\n",p);
    p = NULL;
    return EXIT_SUCCESS;
}

使用gdb一探究竟:

Temporary breakpoint 1, main () at pointer.c:16
16      scanf("%s",p);
(gdb) p p
$1 = 0x0
(gdb) p &p
$2 = (char **) 0x7fffffffdcc8

可以发现p本身是放在栈中的,不过其指向的地址是在代码段中(0x0),而代码段的内存是不能被我们写入的。

字节对齐

一个有趣的例子:

int main(void) {
    char s1[6] = "hello";
    char s2[10] = "world";
    scanf("%s",s1);
    printf("s1: %s, s2: %s\n",s1,s2);
    return EXIT_SUCCESS;
}
17      scanf("%s",s1);
(gdb) p &s1
$5 = (char (*)[6]) 0x7fffffffdca0
(gdb) p &s2
$6 = (char (*)[10]) 0x7fffffffdcb0
#字节对齐
(gdb) x/17c 0x7fffffffdca0
0x7fffffffdca0: 104 'h' 101 'e' 108 'l' 108 'l' 111 'o' 0 '\000'    0 '\000'0 '\000'
0x7fffffffdca8: 0 '\000'    0 '\000'    0 '\000'    0 '\000'    0 '\000'    0 '\000'    0 '\000'    0 '\000'
0x7fffffffdcb0: 119 'w'
#连续存储,越界存储
(gdb) n
0123456789abcdefgh
18      printf("s1: %s, s2: %s\n",s1,s2);
(gdb) n
s1: 0123456789abcdefgh, s2: gh

经典的字节对齐问题,这篇博客不错:
http://blog.csdn.net/andy572633/article/details/7213465

0
0
查看评论
发表评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场

C语言再学习 -- 内存管理

的风
  • qq_29350001
  • qq_29350001
  • 2016-11-07 15:03
  • 642

C学习笔记——malloc内存分配

鉴于上次领导告诉一个解决方案,让我把它写成文档,结果自己脑子里知道如何操作和解决,但就是不知道如何用语言文字把它给描述出来。决定以后多写一些笔记和微博来锻炼自己的文字功底和培养逻辑思维,不然只会是一个敲代码的,永远到不了管理的层面。      ...
  • a10615
  • a10615
  • 2014-11-13 01:55
  • 9446

C语言中内存分配

在任何程序设计环境及语言中,内存管理都十分重要。在目前的计算机系统或嵌入式系统中,内存资源仍然是有限的。因此在程序设计中,有效地管理内存资源是程序员首先考虑的问题。 第1节主要介绍内存管理基本概念,重点介绍C程序中内存的分配,以及C语言编译后的可执行程序的存储结构和运行结构,同时还介绍了堆空间和栈空...
  • youoran
  • youoran
  • 2013-09-03 15:50
  • 98258

C 内存管理详解

C 内存管理详解伟大的Bill Gates 曾经失言:  640K ought to be enough for everybody — Bill Gates 1981   程序员们经常编写内存管理程序,往往提心吊胆。如果不想触雷,唯一的解决办法就是发现所有潜伏的地雷并且排除它们,躲是躲不了的。本文...
  • maybe3is3u5
  • maybe3is3u5
  • 2016-07-29 17:35
  • 644

C语言系列(五)内存的分配与释放

转载请标明出处: http://blog.csdn.net/u011974987/article/details/52290724 本文出自:【Xuhao的CSDN博客】 首先我们来科普一下:什么是堆?说到堆,又忍不住说到了栈!什么是 栈?1、什么是堆:堆是大家共有的空间,分全局堆和局部堆...
  • u011974987
  • u011974987
  • 2016-08-23 14:41
  • 8253

【深度学习】为什么深度学习需要大内存?

介绍深度学习中内存的开销来源,以及降低内存需求的几种解决方案。
  • shenxiaolu1984
  • shenxiaolu1984
  • 2017-05-12 15:30
  • 2546

C语言内存分布图

别的不多说了,图比文字更具有描述力,自己看! 一直都把堆栈放一起,所以很多人会误以为他们的组合是一个词语,就像“衣服”一样简单,其实不然,今天在下就将最近学习总结的一些与大家分享。       一个由C/C++编译的程序占用的内...
  • yanbober
  • yanbober
  • 2013-03-24 16:59
  • 7954

C/C++内存管理之内存池

C++内存管理一直是我比较困惑的问题。俗话说初生牛犊不怕虎,做点啥都new一个,然后delete一个。根本不知道底层会有怎么样的运行机制,慢慢地学习才知道以前学习中有一些东西是不可能在工业中应用的。所以想开辟一个坑为C++的内存管理机制。也希望通过这篇能够对C++内存管理机制有一个更加深刻全新的认识...
  • bateerBATEER
  • bateerBATEER
  • 2017-03-28 21:38
  • 235

C语言内存的初始化

我们编写C语言的时候需要给变量申请一块内存区域,当我们创建一个内存区域的时候,内存中的数据十有八九是乱七八糟的(因为其他代码用过后遗留的数据并没有及时清掉) 例如: int main() { char str[10];//分配的10个字节的内存可能被用过; printf(&quo...
  • baidu_34919559
  • baidu_34919559
  • 2016-05-08 02:17
  • 1061

关于C语言内存移动函数的写法详解

-------------------------------------------------------------------------------------------          &...
  • mzxs131111
  • mzxs131111
  • 2016-10-24 19:50
  • 547
    个人资料
    • 访问:331291次
    • 积分:8856
    • 等级:
    • 排名:第2517名
    • 原创:575篇
    • 转载:13篇
    • 译文:0篇
    • 评论:36条
    我的链接
    最新评论