转载请注明出处:http://blog.csdn.net/mr_liabill/article/details/48751843 来自《LiaBin的博客》
内存分配
堆,栈,静态区,常量区
1、栈:是由编译器在需要时自动分配,不需要时自动清除的变量存储区。通常存放局部变量、函数参数等。
2、堆:由malloc等分配的内存块,用free来释放,程序员需要手动分配,手动释放,否则会造成内存泄漏
3、静态存储区:全局变量和静态变量被分配到同一块内存中。
4、常量存储区:这是一块特殊存储区,里边存放常量,不允许修改。
经典例子
int a = 0; 全局初始化区
char *p1; 全局未初始化区
main()
{
int b; 栈
char s[] = "123"; 栈
char *p2; 栈
char *p3 = "123456"; “123456\0”在常量区,p3在栈上。
static int c =0; 全局(静态)初始化区
p1 = (char *)malloc(10); 堆
p2 = (char *)malloc(20); 堆
}
栈内存: 4个字节给b,3个字节给s,接下来分别给p2.p3分配8个字节地址
堆内存: (char *)malloc(10)+(char *)malloc(20) 总共分配30个字节内存地址
常量区: “123456” 注意默认应该还有'\0'
静态变量区: 4个字节给a,8个字节给p1,4个字节地址给c
1. 指针变量,内存分配8个地址。同时编译器并不为指针所指向的对象分配空间,只为分配指针本身的空间
2. 字符串默认有'\0'
3. 字符串初始化指针,此时字符串视为常量,放在常量区。如果初始化数组,则该字符串不能视为常量,放在栈中。
4. static修饰的局部变量,该自动变量变为静态变量
5. 静态变量如果未初始化,则默认为0。自动变量,随机一个值。
自动变量,静态变量
静态变量
在编译时就确定,全局变量或者代码块内部的static修饰变量,如果没初始化,默认为0
自动变量
随机一个初始值,在运行时才确认
int *p;
所以指针p可能指向内存中任何一个地址,所以如果此时×p,可能会异常退出
static修饰代码块之外的变量声明(也就是全局变量)或者函数定义时
那么从external改为internal,外部变为内部,也就是该变量或者函数只能在本文件中使用,由外部属性变为内部属性
static修饰代码块内部的变量声明
自动变量变为静态变量,就是在函数执行前创建,但是作用域不变,所以该变量只能在代码块中访问,代码块之外不能访问,虽然该变量一直存在!编译期间就已经确认了值,存储在静态区。如果未初始化,初始值为0
C语言中的字符串常量
字符串使用
字符串常量作为右值的情况下,一般都是返回char *指针。同时该字符串常量存储在字符串常量区,不可修改,举例如下
char *p = "test"; 此时p指向字符串常量区“test"的首地址。如果*(p+1)=‘b' -->异常,因为字符串常量不能修改
如果字符串用来初始化数组,那么不视为常量。
char a[]="test"; 如果a不是静态变量,那么"test“存储在栈中,所以a[1]='b' 是正确的 完全等同于char a[]={'t','e','s','t','\0'}
int i="A";
int j="A";
printf("i=0x%x\n",i);
printf("j=0x%x\n",j);
打印:i=0x40077a j=0x40077a
此时说明共用同一个常量区的”A“,int i = "A"; 这样是合理的,此时变量i的内容就是”A“这个字符串的首地址,也说明字符串返回的是返回char *指针,注意跟int i = 'A'的区别
系统自动为“字符串”最后增加’\0’
char name[]="test";
printf("length of name:%s\n",name);
printf("length of name:%d\n",strlen(name));
printf("length of name:%d\n",sizeof(name));
输出:test,7,8
注意
1.sizeof 表示实际占用内存空间的大小,所以此时是5 strlen会一直查找直到出现'\0'终止,所以此时是4 printf("%s")也一样,直到出现'\0'
char name[]={'t','e','s','t'};
printf("length of name:%s\n",name);
printf("length of name:%d\n",strlen(name));
printf("length of name:%d\n",sizeof(name));
第一行打印,不可预料,因为"test"之后没法预料什么时候出现'\0',第二行同第一行,第三行打印4,表示该数组实际上占用4个内存单元
strlen sizeof区别
char name[10];
char *np=name;
printf("name:strlen=%d\n", strlen(name));
printf("np:strlen=%d\n", strlen(np));
这取决于当程序运行时name中的数组存储的是什么值,如果是静态变量,因为数组元素初始化为'\0',那么输出结果铁定是0,如果是自动变量,恰好第一个元素存储的是’\0’,那么结果为0。如果不是,那么结果就难预料了。因为strlen从字符串第一个地址开始查找,直到‘\0’为止,注意不包括’\0’
但是sizeof在编译时就确定,不关心存储的内容具体是多少,只关心存储的字节是多少
所以sizeof(name) 结果为10
如果sizeof(np) 注意此时输出的是8,因为此时表示的是指针变量的内存空间
如果sizeof(*np) 输出的却是1,因为表示的是np指向元素的内存空间,此时是char,所以输出1
以下方式可以得到数组长度
int i[]={1,2};
printf("%d",sizeof(i)/sizeof(i[0]));
输出:8
函数参数传递
按值传递
函数参数会复制拷贝实参到内存栈中,所以不管怎么修改都不会影响原值。注意如果这个参数是结构体,那么不要使用按值传递,因为会拷贝一个结构体,如果这个结构体很复杂的话,那么。。。。
按指针传递
参数指针跟实参指针指向同一内存空间,如果形参指向的内容变了,那么实参内存也变了,如果形参指针只是指向不同内容了还是不会影响形参,参考如下代码
extern void test(char *);
int main(void) {
char *mydata="binjing";
printf("&mydata:0x%x\n",&mydata);
printf("mydata email:0x%x\n",mydata);
test(mydata);
printf("mydata new value:%s\n",mydata);
return 0;
}
void test(char t[]){
printf("&t:0x%x\n",&t);
printf("t email:0x%x\n",t);
printf("old value:%s\n",t);
t="zhoujing";
printf("t new email:0x%x\n",t);
}
输出:
&mydata:0x579810c8
mydata email:0x4006fc
&t:0x579810a8
t email:0x4006fc
old value:binjing
t new email:0x40075f
mydata new value:binjing
此时原理一样,都是产生了一个临时变量(形参),临时变量是指针,这个变量的内容跟实参指针指向的内容一样,但是还是不同的符号,所以如果形参指针发生变化不会影响到实参,但如果形参指向的内容发生变化那么一定会影响到实参!
另一个值得关注的问题是, 如果作为函数参数,指针和数组是等价的
malloc函数
malloc函数使用
malloc函数用来动态在堆上分配内存,如果系统可用内存没有足够的内存分配给程序,那么将返回NULL,所以每次使用完之后记得判断是否为NULL,如果有足够的内存,那么返回void *,需要强制类型转换。同时不使用记得使用free释放
#include <stdio.h>
#include <stdlib.h>
int main(void) {
char * cp = (char *) malloc(4 * sizeof(char));
if (cp == NULL) {
printf("内存不足");
} else {
printf("分配成功\n");
printf("cp==%s\n", cp);//此时结果不可预料,因为堆内存中分配的4个内存单元内容不可预料
}
//释放cp指向的那块内存,这块内存就不能被该程序使用,CPU可能把这块内存分配给其他程序使用
free(cp);
//最好的方式就是释放完内存之后,把指针置为NULL,那么就不会出现野指针从而出现不可预料的结果
//cp==NULL;
//如果此时仍然访问cp指向的那块内存,则结果不可预料,此时cp也叫做野指针
//printf("cp==%s\n", cp);
return 0;
}
野指针
未初始化的指针可以称为野指针(wild pointer),但是野指针并不仅仅是未初始化的指针. 一般来说,野指针(dangling pointer, wild pointer)指向的是内存中无效的对象。
比如以下几个例子.最简单的例子,自动变量char *cp;此时cp未初始化,此时cp可以叫做野指针,因为可能指向内存中无效的对象
1. 如果有一段代码如下
char *dp = NULL;
{
char c;
dp = &c;
} /* c falls out of scope */
当程序走出大括号作用域后, 变量c的内存(在栈中)被系统回收,但是dp仍让指向那块内存,这块内存今后很有可能被系统用作其他用途,此时如果*dp,dp指向的内容将不会是你期待的内容,此处,dp可以称为野指针.
2. 第二种情况是用户自己分配内存,然后自己释放内存,释放内存后仍然引用指针指向的内容, 比如
#include <stdlib.h>
void func()
{
char *dp = malloc(A_CONST);
free(dp); /* dp now becomes a dangling pointer */
dp = NULL; /* dp is no longer dangling */
}
在执行完free(dp)后,dp是一个野指针,因为他指向的内存已经被释放掉了,系统可能会把这段内存用于其他地方.这个时候,常用的一种做法是把这个指针设为NULL, 设为NULL的指针可以由程序员使用if (dp == NULL)语句坚持出来, 但是野指针无法监测.
3. 另外一种情况是指针指向函数中分配的栈内存,函数返回后,内存被回收,但是指针指向的地址没有改变,因此这个指针也是野指针.
extern int * mallocInt();
int main(void) {
//此时ip指向自动变量i,此时被分配在栈中,mallocInt函数一过,所以该段内存也是非法的,此时ip也可称为野指针
int *ip = mallocInt();
printf("*ip==%d\n", *ip);
return 0;
}
int * mallocInt() {
int i = 100;
int *ip = &i;
return ip;
}
从以上三点可以总结出, 野指针其实就是就像一段无效的内存(被系统回收), 但是这个指针的值并不是NULL, 因此如果没有立刻把这个指针赋其他有效值,或者NULL值, 以后再次引用野指针就会出现segmentation faults这样的错误
NULL
内存位置为0,编译器确保没有任何变量,所以解引用*i会造成“Segment fault”,因为这个地址的内容没有任何变量特殊指针变量,不指向任何东西!要是一个指针变量为NULL,只需要给它赋值为0。NULL其实是一个int,值为0
int j = NULL;
printf("j=%d",j); 输出0
int * ip=NULL;
printf("ip=%d",*ip); 出现Segment fault
memset和memcpy函数使用
memset主要应用是初始化某个内存空间。
memcpy是用于copy源空间的数据到目的空间中。
strcpy用于字符串copy,遇到‘\0’,将结束。
void *memset(void *s, int ch, size_t n);
函数解释:将 s 中前 n 个字节 (typedef unsigned int size_t)用 ch 替换并返回 s 。
memset:作用是在一段内存块中填充某个给定的值,它是对较大的结构体或数组进行清零操作的一种最快方法.
char *p = malloc (10*sizeof(char));
//printf("*p=%c",*p); //结果是不可预料的,可以通过memset初始化这段在堆内存中分配的内存
memset(p,’’,10);
void *memcpy(void *dest, const void *src, size_t n);
从源src所指的内存地址的起始位置开始拷贝n个字节到目标dest所指的内存地址的起始位置中
函数返回dest的值。
char *p = malloc (10);
char *name = (char *) malloc(11);
// Assign some value to name
memcpy ( p,name,11); // Problem begins here
//memcpy 操作尝试将 11 个字节写到 p,而后者仅被分配了 10 个字节。那么p指针指向的10个地址空间,后面的第11个地址被非法改写,造成不可预料异常
数组,指针
声明,定义区别
定义:特殊的声明,创建对象并为对象分配内存
声明:而是描述其它地方创建的对象,不分配内存
所以
定义int temp[100];定义了100个int的数组
extern int temp[];声明上文的对象,此时不需要提供数组的长度,因为只是声明!
符号概念
任何一个变量在编译的时候都分配了一个内存地址,并把该记录保存在“编译器符号表”中(数组名-特殊存在)。
比如char c=’a’;此时内存单元745cfb6c的内容为’a’,在“符号表”中a代表的就是745cfb6c内存地址.&操作符输出的就是该符号代表该内存单元地址,&c此时就是0x745cfb6c
注意跟*操作符的区别,*操作符只能用于指针变量,取指针实际指向的值
指针步长问题
int main(void) {
int i[] = { 0x61, 0x62, 0x63, 0 };//0x61是'a'的asii码,然后依次是'b','c',0是'\0'的asii码
//char i[] = { 0x61, 0x62, 0x63, 0 }; //这样定义才能得到正确的结果
char *c = i;
printf("sizeof(int)=%d\n", sizeof(int));
printf("sizeof(char)=%d\n", sizeof(char));
printf("c=%s\n", c);
int j[] = { 0x61, 0x62, 0x63, 0 };
printf("j=%s\n", j);
}
输出结果:
sizeof(int)=4
sizeof(char)=1
c=abc
j=a
c的内容就是i数组第一个元素的数组,但是因为c是char *指针,所以它下次查找会一个字长(sizeof(char)==1),所以在下一个字节中找到了’\0’,所以造成目前这个输出
同时也说明另外一个问题: %s 取得话就是 *(i+sizeof(char)) 来依次取得,直到出现 ’\0’总结:*(cp+1) 这里的1并不只是一个内存单元,具体是几个字节是根据cp是什么类型的指针来决定的,所以能取到下一个元素
数组,指针区别
这里面的学问就大了去了,不深入分析,网上大量资料
1. 作为函数参数,数组和指针等价
2. 数组访问效率比指针要高,
int ar[]={1,2,3};
ar[2],*(ar+2)都为3 只是简单的取a+2地址的内容(根据int类型长度*2)
int *ap={1,2,3};
ap[2],*(ap+2)也都为3 先取ap地址内容,然后把该地址+2,取内容
所以说数组效率高一些3. 数组名更多的时候视为符号,而不是指针
int main(void) {
int a[]={1,2};
int i = 1;
int j = 2;
int *z = &j;
printf("&a= %x\n", &a);
printf("&i= %x\n", &i);
printf("&j= %x\n", &j);
printf("&z= %x\n", &z);
printf("a= %x\n", a);
printf("i= %x\n", i);
printf("j= %x\n", j);
printf("z= %x\n", z);
return EXIT_SUCCESS;
}
输出:
&a= 745cfb60
&i= 745cfb6c
&j= 745cfb68
&z= 745cfb58
a= 745cfb60
i= 1
j= 2
z=745cfb68此时数组a和&a的输出结果是一样的,但是指针z输出的是指向内容的地址,&z输出的是z符号的地址
4. 以下情况需要留意
注意如果
char p[]=”binjing”;
另一个c文件中有如下代码:
extern char *p;
p[2]
此时将会出现“不可预料”的结果!因为如果此时p符号的内存地址为0x745cfb60。那么p符号地址中存储的内容就是’b’=62。所以如果p[2]=*(p+2)其实取得就是内存地址为62+2所存储的内容!!!!所以这个结果一定是不可预料的!
指针数组,数组指针
首先[]优先级大于*,()优先级无疑最大
数组指针(也称行指针)
定义 int (*p)[n];
()优先级高,首先说明p是一个指针,指向一个整型的一维数组,这个一维数组的长度是n,也可以说是p的步长。也就是说执行p+1时,p要跨过n个整型数据的长度。
如要将二维数组赋给一指针,应这样赋值:
int a[3][4];
int (*p)[4]; //该语句是定义一个数组指针,指向含4个元素的一维数组。
p=a; //将该二维数组的首地址赋给p,也就是a[0]或&a[0][0]
p++; //该语句执行过后,也就是p=p+1;p跨过行a[0][]指向了行a[1][]
所以数组指针也称指向一维数组的指针,亦称行指针。
指针数组
定义 int *p[n];
[]优先级高,先与p结合成为一个数组,再由int*说明这是一个整型指针数组,它有n个指针类型的数组元素。这里执行p+1是错误的,
这样赋值也是错误的:p=a;因为p是个不可知的表示,只存在p[0]、p[1]、p[2]...p[n-1],而且它们分别是指针变量可以用来存放变量地址。
但可以这样 *p=a; 这里*p表示指针数组第一个元素的值,a的首地址的值。
如要将二维数组赋给一指针数组:
int *p[3];
int a[3][4];
for(i=0;i<3;i++)
p[i]=a[i];
这里int *p[3] 表示一个一维数组内存放着三个指针变量,分别是p[0]、p[1]、p[2]
所以要分别赋值。
这样两者的区别就豁然开朗了,数组指针只是一个指针变量,似乎是C语言里专门用来指向二维数组的,它占有内存中一个指针的存储空间。步长为数组大小
指针数组是多个指针变量,以数组形式存在内存当中,占有多个指针的存储空间。
还需要说明的一点就是,同时用来指向二维数组时,其引用和用数组名引用都是一样的。
比如要表示数组中i行j列一个元素:
*(p[i]+j)、*(*(p+i)+j)、(*(p+i))[j]、p[i][j]
Typedef和define区别
typedef int * MyInt1; //一个语句,如同定义变量一样定义了一种新类型
#define MyInt2 int* //预处理,简单的文本替换,所以没有"分号",不是一个语句
const MyInt1 my1;//my1表示指向int类型的常量指针,指针不能修改,指向的值可以修改,因为MyInt1,表示指向int的指针 等价于==int *const my1;
const MyInt2 my2;//文本替换,const int* my2;所以此时my2是指针常量,指针可以修改,指向的值不能修改 等价于==const int* my2;
结构体Struct
结构指针
typedef struct MyStruct {
int *i;
int j;
char *home;
} MyData;
MyData *md1 ;//此时只是为md分配了内存空间,也就是八个字节
MyData *md2=malloc(sizeof(MyData));//此时既为ms2分配内存,也分配结构体的内存
MyData md3;//此时为md3分配一个结构体的内存
结构指针内存泄漏问题
typedef struct Person {
char *name;
int age;
char *home;
} P;
int main(void) {
P *p = (P*) malloc(sizeof(P));
p->name = "liabin";
p->home = (char*) malloc(10 * sizeof(char));
printf("name=%s\n", p->name);
free(p);
return 0;
}
释放掉(P*) malloc(sizeof(P))之后,所以(char*) malloc(10 * sizeof(char))这段内存就没有指针指向它了,所以没法释放这段内存,造成内存泄漏。
注意malloc分配在堆上的内存都需要手动释放
解决方案:
free(p->home);
free(p);
每当释放结构化的元素,而该元素又包含指向动态分配的内存位置的指针时,应首先遍历子内存位置,并从那里开始释放,然后再遍历回父节点。
边界对齐
参考 点击打开链接