作用域
一个C语言变量的作用域可以使代码块作用域,函数作用域或者文件作用域
代码块是 { } 之间的一段代码。
出现在 { } 之外的变量,就是全局变量。
代码块作用域的静态变量
静态变量是指内存中在程序执行期间一直不改变的变量,一个代码块内部的静态变量只能被这个代码块内部访问。
代码块作用域外的静态变量
代码块之外的静态变量在程序执行期间一直存在,但只能被定义这个变量的文件访问,不能跨文件访问。
全局变量
全局变量的存储方式和静态变量相同,但可以被多个文件访问。
全局函数和静态函数
使用static关键字修饰可将函数声明为静态。
内存四区
计算机中有内存,我们的程序是在内存中执行的,在内存中程序与程序之间不能互相访问,对于某一个程序它可以映射出四个区块来存放这段程序,有代码区(程序中所有可执行代码),静态区(所有的静态变量和全局变量),堆区,栈区。
#include <iostream>
using namespace std;
int a = 0;//静态区
static int b = 0;//静态区
int main()//代码区
{
int c = 0;//栈区
int d = 0;//栈区
static int e = 0;//静态区
printf("%d,%d,%d,%d,%d,%d", &a, &b, &c, &d, &e, main);
//13083748,13083752,7338828,7338816,13083756,13046455
system("pause");
return 0;
}
代码区
程序代码指令、常量字符串、只可读(静态)
程序被操作系统加载到内存的时候,所有的可执行代码都加载到代码区,这块内存是不可以在运行期间修改的。
静态区
存放全局变量/常量、静态变量/常量
所有的全局变量以及程序中的静态变量都存储到静态区
栈区
预先设定大小,自动分配与释放(动态)
栈stack是一种先进后出的内存结构,所有的变量(auto),函数形参都是由编译器自动放到栈中,当一个变量(auto)超出其作用域时,自动从栈中弹出。对于变量的出栈,入栈不需要程序控制,由编译器实现。
int main()
{
int a = 0;
int b = 0;
return 0;
}
上面的代码如下图
注意:在程序中避免出现返回一个栈变量的地址。可以返回一个堆地址,但一定要释放free。
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
int *GetA()//避免这种操作
{
int a = 100;
return &a;
}//a的作用域
int main(int argc, char** argv)
{
int *p = GetA();
//得到了一个临时栈变量的地址,这个地址在函数GetA调用完之后已经无效了
*p = 10;
printf("%d", *p);//存在二义性
return 0;
}
可以这样写
int *GetA()
{
int *p = (int *)malloc(sizeof(int));
return p;
}
int main(int argc, char** argv)
{
int *p = GetA();
*p = 100;
printf("%d\n", *p);
free(p);
printf("hello.. \n");
system("pause");
return 0;
}
栈溢出
当栈空间已满时,还往栈内存中压入变量,这个就叫栈溢出。
对于一个32位操作系统,最大管理管理4G内存,其中1G是给操作系统自己用的,剩下的3G都是给用户程序,一个用户程序理论上可以使用3G的内存空间。
堆区
由程序员控制,使用malloc/new,free/delete来操作(动态)
堆heap和栈一样,也是一种在程序运行过程中可以随时修改的内存区域,但没有栈那样先进后出的顺序。堆是一个大容器,它的容量要远远大于栈,但是在C语言中,堆内存空间的申请和释放需要手动通过代码来完成。
堆的分配和释放
malloc
void * malloc(size_t _Size);
malloc函数在堆中分配参数_Size指定大小的内存,单位:字节,函数返回void *指针。
free
void free(void *p);
free负责在堆中释放malloc分配的内存。参数p为malloc返回的堆中的内存地址。
void PrintArray(int *p,int n)
{
for (int i = 0; i < n; i++)
{
printf("p[%d] = %d\n", i, p[i]);
}
}
int main(int argc, char** argv)
{
//栈数组
int array[10] = { 0 };//代码执行完毕会自动释放
//堆数组
int *p = (int *)malloc(sizeof(int) * 10);//需要手动释放
memset(p, 0, sizeof(int) * 10);
for (int i = 0; i < 10; i++)
{
p[i] = i;
}
PrintArray(p, 10);
char *p1 = (char *)malloc(sizeof(char) * 10);
memset(p1, 0, sizeof(char) * 10);
free(p);
free(p1);
printf("hello.. \n");
system("pause");
return 0;
}
了解C++中的内存分配与释放请移步到:对象的动态建立和释放(new&delete)
分析一下经常容易混淆的模型
如果是静态变量呢?是可以执行的,因为静态成员变量在程序中永远有效。
int *GetB()//可以,因为静态变量在程序中永远有效
{
static int a = 0;
return &a;
}
int main(int argc, char** argv)
{
int *p1 = GetB();
*p1 = 200;
printf("%d\n", *p1);
printf("hello.. \n");
system("pause");
return 0;
}
如果是一下模型呢?
int GetA(int *p)//不可行
{
p = (int *)malloc(sizeof(int)*10);
}
int GetB(int **p)//可行
{
*p = (int *)malloc(sizeof(int)*10);
}
int main(int argc, char** argv)
{
int *p = NULL;
printf("%d\n", &p);
//GetA(p);//实参没有任何改变
GetB(&p);//引用传递,得到了堆内存地址
p[0] = 1;
p[1] = 2;
printf("%d\n", p);
printf("p[0] = %d,p[1] = %d\n", p[0], p[1]);
free(p);
printf("hello.. \n");
system("pause");
return 0;
}
模型分析
int GetA(int *p)
这里的形参int*p属于栈区,当GetA程序执行完时生命周期也就结束了,p就会消失。而在代码块中分配的内存在堆区,p的消失导致它指向的具体堆空间的地址编号也随之消失了。造成了内存泄漏,这块内存无法释放。在main代码中free(p)没有任何意义,因为p自始至终没有分配内存。
int GetB(int **p)
首先理解形参*p访问的是main中的p,形参**p访问的是main中的*p,前者指向的是其他变量的地址,后者指向的是变量*p的地址。在执行GetB(&p)时得到了堆内存的地址,GetB代码块中最关键的一步是*p = malloc,这里的*p所指的是main中p的地址,执行完成之后,main中的p就得到了堆内存地址,从而可以进行赋值和释放。
memset
将一块内存初试化,最常见的方法
memset(void *_Dst,int _Val,size_t_Size);
//第一个参数是要设置的内存地址,第二个是要设置的值,第三个是初始化的内存大小
//main
int array[100] ={1,54,21,78,32,326,526,66,55};
memset(array,0,sizeof(array));
//将一块内存初试化为0,最常见的方法
memcpy
内存拷贝
///main
int buf1[10] = {1,2,3,4,5,6,7,8,9,10};
int buf2;
memcpy(buf2,buf1,sizeof(buf1));
//将buf1的内存内容全部拷贝到buf2,拷贝大小sizeof(buf1)
//在使用内存拷贝时,一定要确保内存没有重叠区域
memmove
内存移动
main
int buf1[10] = {1,2,3,4,5,6,7,8,9,10};
int buf2[10];
memmove(buf2,buf1,sizeof(buf1));
//将buf1的内存内容全部移动到buf2,移动需要的大小为sizeof(buf1)