目录
C++内存分布
变量的四种存储方式
函数返回值使用指针(指针函数)
动态分配内存空间
不能使用外部函数的普通局部变量的地址
通过指针函数返回静态局部变量的地址
动态内存
根据需要分配内存,不浪费(根据用户的需求设置内存的容量)
被调用函数之外需要使用被调用函数内部的指针对应的地址空间
补充:指针函数和函数指针不同(详细例子请看第四站,函数指针):
突破栈区限制,可以给程序分配更多内存
动态内存的分配使用和释放
new 和 delete 运算符使用的一般格式为:
内存泄漏
当忘记释放内存
当开启释放内存
内存检查工具
VS自带的
内存泄漏工具
C++内存分布
1、栈区(stack):由编译器自动分配释放,存放函数的参数值,局部变量值等。
2、堆区(heap):一般由程序员分配释放,随叫随到,挥之即走(动态内存)。
3、全局/静态区(static):全局变量和静态变量的存储是放在一起的,在程序编译时分配。
4、文字常量区:存放常量字符串。
5、程序代码区:存放函数体(包括类的成员函数、全局函数)的二进制代码
变量的四种存储方式
分别为自动变量(auto)、寄存器变量(register . 静态变量 (static)、外部变量(extern))。
auto - 函数中所有的非静态的局部变量。(auto一般省略(c11以上不用写auto会报错,因为内部已经含有))
register - 一般经常被使用的的变量(如某一变量需要计算几千次)可以设 置成寄存器变量,register 变量会被存储在寄存器中,计算速度远快于存在内存 中的非 register 变量。
C++ 的 register 关键字已经优化,如果我们打印它的地址,它就变成了普通的 auto 变量
static - 在变量前加上 static 关键字的变量。
extern - 把全局变量在其他源文件中,声明成 extern 变量,可以扩展该全局变量的作用域至声明的那个文件,其本质作用就是对全局变量作用域的扩展。
#include <iostream>
using namespace std;
//外部变量,可以用本文件其他源文件中的全局变量
extern int extern_a;//这里不能再给这个外部变量赋值
//静态全局变量
static int c = 18;
//寄存器变量
void register_demo() {
register int i = 1;
//寄存器变量本身没有地址
// C++ 的 register 关键字已经优化,如果我们打印它的地址,它就变成了
//普通的 auto 变量
cout << "寄存器变量i的地址值:" << &i << endl;
}
//静态局部变量
void static_demo() {
//静态变量,只会初始化一次,也就是这条语句只会执行一次,
//被static的变量值,在函数体执行完后不会释放,下次执行改函数体,
//这个变量可以用上一次执行函数体的值
static int a = 18;
int b = 18;
a++;
b++;
c++;
cout << "a作为静态局部变量的值:" << a << endl;
cout << "c作为静态全局变量的值:" << c << endl;
cout << "b作为局部变量auto的值:" << b << endl;
}
void extern_demo() {
extern_a++;
cout << "外部变量的值:" << extern_a << endl;
}
int main(void) {
int i = 18;//c语言可以写上auto也不会报错,但是c++做了升级写了auto会报错
cout << "局部变量的值:" << i << endl;
cout << endl;
register_demo();
cout << endl;
for (int i = 0; i < 3; i++) {
static_demo();
cout << endl;
}
cout << endl;
extern_demo();
}
函数返回值使用指针(指针函数)
可以返回函数内部:动态分配内存地址 局部静态变量地址 以及全局静态变量和外部变量 地址
动态分配内存空间
#include <iostream>
#include <stdlib.h>
using namespace std;
//返回动态内存分配地址
int* add1(int x, int y)
{
int* sum = NULL;
sum = new int;
*sum = x + y;
return sum;
}
int main()
{
int a = 3, b = 5;
int* sum = NULL;
//接收外部函数动态内存分配的地址 ok
sum = add1(a, b);
cout<<*sum<<endl;
delete sum;
system("pause");
return 0;
}
不能使用外部函数的普通局部变量的地址
(普通局部变量的值在函数调用结束后值会释放)
(这样是错误的 vs版本升级后,这种写法会触发断点,但是以前的不会),但是依然会运行出结果,这种结果会被栈空间覆盖掉(如果后面有其他函数用到这片空间)那么这个函数所运行的结果也会被后来的函数值覆盖掉,运行出来的值并不正确
#include <iostream>
#include <stdlib.h>
using namespace std;
int* add(int x, int y) {
int sum = x + y;
return ∑
}
//程序动态动态分配一块内存空间,这片空间会覆盖在上面函数在调用结束后,释放的空间之上
int* add1(int x, int y)
{
int* sum = NULL;
sum = new int;
*sum = x + y;
return sum;
}
int main()
{
int a = 3, b = 5;
int* sum = NULL;
//不能使用外部函数局部变量的地址(这样是错误的)
sum = add(a, b);
//如果再输出这个外部函数局部变量的值之前,调用一片由程序员动态分配内存的指针函数,那么这片申
//请的内存空间会覆盖掉上面函数的之前使用的空间,
add1(a,b);
cout<<*sum<<endl;//这里输出的值就会受到上面这个函数的影响从而返回错误的值
delete sum;
system("pause");
return 0;
}
通过指针函数返回静态局部变量的地址
:这种方法是可以的,不同于上面这种普通局部变量,静态变量的特点:(只会初始化一次,但是当函数调用结束后,运行出来的值并不会释放)
#include <iostream>
#include <stdlib.h>
using namespace std;
//通过指针函数返回静态局部变量的地址
int* add(int x, int y)
{
static int sum = 0;//静态变量的值不会因为函数调用结束而释放
cout << "函数内部的值:" << sum << endl;
sum = x + y;
return ∑
}
int main()
{
int a = 3, b = 5;
int* sum = NULL;
sum = add(a, b);
cout << "第一次调用静态后的函数值:" << *sum << endl;
*sum = 8888;
sum = add(a, b);
cout << "第二次调用静态后的函数值:" << *sum << endl;
return 0;
}
动态内存
根据需要分配内存,不浪费(根据用户的需求设置内存的容量)
#include <iostream>
using namespace std;
int main(void) {
int a[] = { 11,2,34,12,18,19,17,10 };
int len = sizeof(a) / sizeof(a[0]);
int num = 0;
//1.按需分配,根据需要分配内存,不浪费
int* salary = NULL;
cout << "请输入内存数:" << endl;
cin >> num;
//判断输入的数是否大于数组的长度,不大于则输入不合法
//需要按需分配内存,就得大于本来数组的长度
if (num < len) {
cout << "输入数字不合法!.." << endl;
}
//第一种:使用指针形式逐个赋值
salary = new int[num];
for (int i = 0; i < len; i++) {
*(salary + i) = a[i];
}
//将多出a数组的值,赋值为18
for (int i = len; i < num; i++){
*(salary + i) = 18;
}
for (int i = 0; i < num; i++) {
cout << "输出第" << i + 1 << "的值为:" << *(salary + i)<<endl;
}
cout << endl;
//第二种:使用内存拷贝函数,c提供
//从源 a 所指的内存地址的起始位置开始拷贝 len 个字节到目标 salary 所指的
//内存地址的起始位置中
memcpy(salary, a, len);
for (int i = len; i < num; i++) {
*(salary + i) = 18;
}
for (int i = 0; i < num; i++) {
cout << "输出第" << i + 1 << "的值为:" << *(salary + i)<<endl;
}
delete[] salary;
}
void *memcpy(void *dest, const void *src, size_t n); #include <string>
功能:从源 src 所指的内存地址的起始位置开始拷贝 n 个字节到目标 dest 所指的 内存地址的起始位置中
被调用函数之外需要使用被调用函数内部的指针对应的地址空间
#include <iostream>
using namespace std;
/*
2.被调用函数之外需要使用被调用函数内部的指针对应的地址空间
*/
//指针函数返回动态内存
int* demo(int count) {
int* pointer = NULL;
//通过c++的方式申请内存空间
pointer = new int[count];
//通过c语言的方式申请内存空间
//pointer = (int*)malloc(sizeof(int) * count);
for (int i = 0; i < count; i++) {
*(pointer + i) = 100 + i;
}
for (int i = 0; i < count; i++) {
cout << "通过指针函数返回的值" << *(pointer + i) << endl;
}
return pointer;
}
//通过二级指针
void demo1(int count, int** pointer1) {
int* ap = NULL;
*pointer1 = new int[count];
ap = *pointer1;
for (int i = 0; i < count; i++) {
*(ap + i) = 100 + i;
}
for (int i = 0; i < count; i++) {
cout << "通过二级指针返回的值" << *(ap + i) << endl;
}
}
int main(void) {
int* pointer1 = NULL;
int count = 10;
//cout << "通过指针函数调用返回的值:" << endl;
//pointer1 = demo(count);
//cout << endl;
//for (int i = 0; i < count; i++){
// cout << "通过指针函数调用返回的值:" << *(pointer1 + i) << endl;
//}
cout << "通过二级指针调用返回的值:" << endl;
demo1(count, &pointer1);
cout << endl;
for (int i = 0; i < count; i++) {
cout << "通过二级指针调用返回的值:" << *(pointer1 + i) << endl;
}
delete pointer1;//要释放内存
return 0;
}
补充:指针函数和函数指针不同(详细例子请看第四站,函数指针):
函数指针是指向函数的指针变量,用于存储和调用函数。
指针函数是返回值为指针类型的函数,用于返回不同的指针值或函数指针。
函数指针可以作为一个指针类型,成为指针函数的类型,
突破栈区限制,可以给程序分配更多内存
函数分配的栈空间大小一般都有限制,Windows一般为1-2M,但是可以使用动态内存分配,给函数分配更多的内存空间(也不能太大,程序员动态分配的内存空间大小一般为2个G左右,也就是堆空间大小为2G左右)一个函数可供分配就1G左右,当写到第三的时候就会出现运行出错
#include <iostream>
using namespace std;
/*
3.突破栈区限制,可以给程序分配更多内存
*/
void demo() {
int* ap = NULL;
ap = new int[1024 * 1024*1000*1.02];//1个G是能分配的,但是当到达1.03G就会报错
}
void demo1() {
int* ap = NULL;
ap = new int[1024 * 1024 * 1000 * 1.02];//1个G是能分配的,但是当到达1.03G就会报错
}
int main(void) {
demo();
demo1();
return 0;
}
当分配三个时候:程序报错bad_alloc,这个就是动态内存溢出的意思
动态内存的分配使用和释放
在 C 语言中是利用库函数 malloc 和 free 来 分配和撤销内存空间的。C++提供了较简便而功能较强的运算符 new 和 delete 来 取代 malloc 和 free 函数。(支持互相混合使用的)
(注意: new 和 delete 是运算符,不是函数,因此执行效率高。)
new 和 delete 运算符使用的一般格式为:
new 运算符 动态分配堆内存 使用方法:
指针变量 = new 类型(常量);//常量可缺省
指针变量 = new 类型[表达式]; //数组
指针变量 = new 类型[表达式][表达式] //二维数组
delete 运算符 释放已分配的内存空间 使用方式:其中“指针变量” 必须时一个 new 返回的指针!
普通类型(非数组)使用: delete 指针变量;
数组 使用: delete[] 指针变量;
#include <iostream>
using namespace std;
int main(void) {
//第一种分配动态内存不执行初始化
int* p1 = new int;
*p1 = 100;
//第二种分配动态内存同时执行初始化
int* p2 = new int(100);
// 第三种 malloc 返回值是 void *
int* p3 = (int*)malloc(sizeof(int));
free(p1); //基础类型可以 new free 可以混搭
delete p3; //基础类型可以 malloc delete 可以混搭
delete p2; //free(p2); 同样效果
int **p4 = new int*[1];
free(p4);
return 0;
}
内存泄漏
在企业开发中,一个项目往往需要一天24小时不断运行,如果忘记内存释放,程序内存就会一直运行,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
当忘记释放内存
#include <iostream>
#include <Windows.h>
using namespace std;
//用来记录没有释放内存的
void demo() {
int* p1 = NULL;
p1 = new int[1024*10];
p1[0] = 1;
}
int main(void) {
for (int i = 0; i < 102400; i++){//便于观察内存泄漏情况
demo();
Sleep(5);
}
return 0;
}
当开启释放内存
#include <iostream>
#include <Windows.h>
using namespace std;
void demo1() {
int* p1 = NULL;
p1 = new int[1024*10];
p1[0] = 0;
delete[] p1;
}
int main(void) {
for (int i = 0; i < 102400; i++){//便于观察内存泄漏情况
demo1();
Sleep(5);
}
return 0;
}
内存检查工具
VS自带的
VisualC++ debugger 和 CRT 库
第一步: 包含以下头文件 :debug需要在debug模式下调试才能看到内存泄漏的信息
#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>
第二步: 接管 new 操作符
#ifdef _DEBUG
#ifndef DBG_NEW
#define DBG_NEW new ( _NORMAL_BLOCK , __FILE__ ,__LINE__)
#define new DBG_NEW
#endif
#endif
第三步: 在代码结束出输出内存泄漏信息
_CrtDumpMemoryLeaks();
#include <iostream>
#include <Windows.h>
#include <stdlib.h>
#include <crtdbg.h>
#define _CRTDBG_MAP_ALLOC_
using namespace std;
#ifdef _DEBUG
#ifndef DBG_NEW
#define DBG_NEW new ( _NORMAL_BLOCK , __FILE__ ,__LINE__)
#define new DBG_NEW
#endif
#endif
//用来记录没有释放内存的
void demo() {
int* p1 = NULL;
p1 = new int[1024 * 100];
p1[0] = 1;
}
int main(void) {
for (int i = 0; i < 5; i++) {//便于观察内存泄漏情况
demo();
Sleep(5);
}
_CrtDumpMemoryLeaks();
return 0;
}
内存泄漏工具
内存泄漏工具: Windows : Purify,BoundsCheaker、Deleaker、VisualLeak Detector(VLD), Linux 平台:Valgrind memcheck