开发环境
- Ubuntu 14.04(32bits)
- GCC
- 编辑器 Cmd Markdown
- 画图工具 Processon
1,数组内存布局
上一节 简单介绍了结构体作为函数参数和返回值的情况。本文准备介绍一下数组的内存布局,即静态数组/动态数组和一维数组/二维数组,顺便介绍一下0长度数组的妙用。
1.1静态一维数组和动态二维数组
静态一维数组,即类似于int a[10];动态数据,即类似于int p=(int)malloc(10sizeof(int));(或者int p=new int[10]);
1.1.1静态一维数组
代码语言:javascript复制#include<stdio.h>
int main()
{
int a[3];
a[0]=1;
a[1]=2;
a[2]=3;
printf("%d %d %dn",a[0],a[1],a[2]);
}
对应的汇编代码为
代码语言:javascript复制 .file "c2-0.c"
.section .rodata
.LC0:
.string "%d %d %dn"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushl �p
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, �p
.cfi_def_cfa_register 5
andl $-16, %esp
subl $32, %esp
movl $1, 20(%esp) //esp 20即为p[0]的地址
movl $2, 24(%esp) //esp 24即为p[1]的地址
movl $3, 28(%esp) //esp 28即为p[2]的地址
movl 28(%esp), �x
movl 24(%esp), �x
movl 20(%esp), �x
movl �x, 12(%esp) //将ecx压栈,即[esp 12]=ecx
movl �x, 8(%esp) //将edx压栈,即[esp 8]=edx
movl �x, 4(%esp) //将eax压栈,即[esp 4]=eax
movl $.LC0, (%esp) //类似于“%d %d”字符串的地址
call printf
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu 4.8.4-2ubuntu1~14.04.1) 4.8.4"
.section .note.GNU-stack,"",@progbits
main函数执行时,栈帧为:
1.2 动态一维数组
代码语言:javascript复制#include<stdio.h>
#include<stdlib.h>
int main()
{
int *p=(int*)malloc(sizeof(3*sizeof(int)));
p[0]=1;
p[1]=2;
p[2]=3;
free(p);
}
对应的汇编代码为
代码语言:javascript复制 .file "c2-1.c"
.text
.globl main
.type main, @function
main:
.LFB2:
.cfi_startproc
pushl �p
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, �p
.cfi_def_cfa_register 5
andl $-16, %esp
subl $32, %esp
movl $12, (%esp) //将12压栈
call malloc //调用malloc函数
movl �x, 28(%esp) //eax为malloc在堆上分配空间的首地址
,esp 28即为p的地址
movl 28(%esp), �x //获取p,即p指向的内存的首地址
movl $1, (�x) //即p[0]=1
movl 28(%esp), �x //p
addl $4, �x //p =4
movl $2, (�x) //即p[1]=2
movl 28(%esp), �x
addl $8, �x
movl $3, (�x) //p[2]=3
movl 28(%esp), �x
movl �x, (%esp) //将p压栈,即p指向的堆上内存的首地址
call free
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE2:
.size main, .-main
.ident "GCC: (Ubuntu 4.8.4-2ubuntu1~14.04.1) 4.8.4"
.section .note.GNU-stack,"",@progbits
通过对比静态一维数组,和动态一维数组,可以知道,静态数组名为数组的首地址,但是并不占用内存(据此,可以实现0长度数组的妙用)。动态二维数组,在堆上分配的首地址保存在指针内,需要分配内存。
1.1.3 0长度数组的妙用
代码语言:javascript复制typedef struct test{
int i;
int j;
unsigned char ch[0];
}test;
其中的ch[0]即为0长度数组,sizeof(test)=8,
即ch为一个任意大小内存的指针,但unsigned ch[0]
并不占用test结构体变量的内存。
其用法如下:
int main()
{
test *p=(test*)malloc(12);
p->i=1;
p->j=2;
*((int*)(p->ch))=3;
printf("%un",sizeof(test));
printf("%d %d %dn",p->i,p->j,*((int*)(p->ch)));
}
以上结构体指针p指向的堆上内存布局为:
1.2静态二维数组和动态二维数组
1.2.1静态二维数组
代码语言:javascript复制静态二维数组的内存布局即为一维数组,
假设int p[3][4];int *px; 另px=p;
则访问p[2][1]的元素,可以转换为px[2*16 1*4]元素。
int main()
{
int p[3][4];
p[1][0]=123;
/*
1, p 1是二维数组p中序号为1的行的首地址,
而*(p 1)并不是p 1单元的内容,*(p 1)可以
理解为由行地址的计算转向了列地址的计算。
如:*(p 1)表示第1行,第0列的地址。
2, 不要把&p[i]简单的理解为p[i]单元的地址,
因为并没有给p[i]分配内存。&p[i]可以理解为
由当前的列地址计算转向了行地址计算(&(*(p i))=p i)。
*/
printf("%xn%xn%xn%xn",p[1],p 1,&p[1][0],&p[1]);
return 0;
}
以上代码对应的内存布局为:
1.2.2动态二维数组
代码语言:javascript复制#include<stdio.h>
#include<stdlib.h>
int main()
{
int **p=NULL;
p=(int**)malloc(sizeof(int*)*3);
p[0]=(int*)malloc(sizeof(int)*4);
p[1]=(int*)malloc(sizeof(int)*4);
p[2]=(int*)malloc(sizeof(int)*4);
printf("%xn%xn%xn%xn",p[1],p 1,&p[1][0],&p[1]);
free(p[0]);
free(p[1]);
free(p[2]);
free(p);
/* int **p=NULL;
p=new int*[3];
p[0]=new int[4];
p[1]=new int[4];
p[2]=new int[4];
*/ /*
1, p 1是二维数组p中序号为1的行的首地址,而*(p 1)是p 1单元的内容。
2, &p[i]可以理解为p[i]单元的地址。
*/
/*
printf("%xn%xn%xn%xn",p[1],p 1,&p[1][0],&p[1]);
delete[] p[0];
delete[] p[1];
delete[] p[2];
delete[] p;
*/
return 0;
}
以上代码对应的内存布局为: