C言語学習への道(基礎) - メモリ管理

注: このブログはブロガーによって一字一句書かれており、簡単ではありませんが、オリジナリティを尊重してください。

範囲

C言語変数のスコープは次のように分割されます。

  • コード ブロックのスコープ (コード ブロックは {} の間にあるコードのセクションです)
  • 関数スコープ
  • ファイルスコープ

1) ローカル変数

ライフ サイクル: スペースをいつ開くか (誕生)、スペースを解放するか (死)、このプロセスはライフ サイクルと呼ばれます。
ローカル変数は auto 自動変数 (auto 記述できるかどうかに関係なく) とも呼ばれ、通常はコード ブロック < a i=4>内部定義された変数はすべて自動変数であり、次の特性があります。{}

  • スコープ: 関数内で定義された場合は、関数スコープ内でのみ有効です。複合ステートメントで定義された場合は、複合ステートメント内でのみ有効です。
  • ライフ サイクル:プログラムは変数定義まで実行され、スペースが空きます。関数呼び出しの終了または複合ステートメントの終了により、ローカルのライフ サイクルが終了します。変数も終了し、スペースが解放されます。
  • 初期値が割り当てられていない場合、内容はランダムになります。

例 1:

#include <stdio.h>

void test()
{
    
    
	//auto写不写是一样的
	//auto只能出现在{}内部
	auto int b = 20; 
	return;
}

int main(){
    
    
	
	//b = 100; //err, 在main作用域中没有b
	//int c;
	//printf("c=%d\n", c); // 未初始化的值   随机
	int* p = NULL;
	if (1)
	{
    
    
		//在复合语句中定义,只在复合语句中有效
		int a = 10;
		int d = 30;
		p = &d;
		printf("a = %d\n", a);
	}

	//a = 10; //err 离开if()的复合语句,a已经不存在
	*p = 300;
	printf("%d", *p);
	return 0;
}
输出结果
a = 10
300

2) 静的ローカル変数

  • スコープ:staticローカル変数のスコープは、定義された関数内でも有効です
  • ライフ サイクル:staticローカル変数のライフ サイクルは、プログラムの実行サイクルと同じです。スペースは、 main 関数。プログラムが終了して領域を解放すると、同時にstaticローカル変数の値は 1 回だけ初期化されますが、複数回割り当てることができます
  • staticローカル変数に初期値が割り当てられていない場合、システムによって自動的に割り当てられます。数値変数には自動的に初期値が割り当てられます0、文字変数には null が割り当てられます。キャラクター

例 1:

#include <stdio.h>

void fun1()
{
    
    
	int num1 = 1;
	num1++;
	printf("num1 = %d\n", num1);
}

void fun2()
{
    
    
	//静态局部变量,没有赋值,系统赋值为0,而且只会初始化一次
	static int num2 = 1;
	num2++;
	printf("num2 = %d\n", num2);
}

int main(void)
{
    
    
	static int n;
	printf("n = %d\n", n); // n = 0
	fun1(); // num1 = 2
	fun1(); // num1 = 2
	fun2(); // num2 = 2
	fun2(); // num2 = 3
	
	return 0;
}
输出结果
n = 0
num1 = 2
num1 = 2
num2 = 2
num2 = 3

3) グローバル変数

  • スコープ: 関数の外部で定義され、このファイルと他のファイル (プロジェクト全体のすべてのファイル) の関数で共有できます。他のファイルの関数がこの変数を呼び出す場合は、extern
  • ライフサイクル: グローバル変数のライフサイクルはプログラムの実行サイクルと同じで、main 関数の実行前に空間が開かれ、プログラム終了後に空間が解放されます。
  • グローバル変数に初期値が割り当てられていない場合、システムによって自動的に割り当てられます。数値変数には自動的に初期値が割り当てられます0、文字変数には null が割り当てられます。キャラクター
  • 異なるファイル内のグローバル変数に同じ名前を付けることはできません。

例 1:

#include <stdio.h>

int num;

void test01() {
    
    


	num = 10;
	printf("num = %d\n", num);

}

int main() {
    
    
	printf("num = %d\n", num); // 0
	test01(); // 10
	

	return 0;
}

例 2:

#include <stdio.h>

extern int num; // 声明num在其他文件定义过
int main() {
    
    

	num = 100;
	printf("num = %d\n", num);

	return 0;
}

ここに画像の説明を挿入します

4) 静的グローバル変数

  • スコープ: 関数の外部で定義され、スコープは定義されたファイル (現在のファイル) に制限されます。異なるファイル内の静的グローバル変数は同じ名前を持つことができますが、スコープは異なります。対立ではない
  • ライフ サイクル:staticグローバル変数のライフ サイクルは、プログラムの実行サイクルと同じです。スペースは main 関数の実行前に開かれ、メイン関数の実行後に解放されます。プログラムは終了します。同時に< a i=2>グローバル変数の値は 1 回だけ初期化されますstaitc
  • staticグローバル変数に初期値が割り当てられていない場合、システムによって自動的に割り当てられます。数値変数には自動的に初期値が割り当てられます0、文字変数には null が割り当てられます。キャラクター

例 1:

#include <stdio.h>

static int num2;

void test02() {
    
    


	num2 = 20;
	printf("num2 = %d\n", num2);

}

int main() {
    
    
	printf("num2 = %d\n", num2); // 0
	test02(); // 20
	
	return 0;
}

例 2:

#include <stdio.h>

extern int num2; // error 静态全局变量不能进行声明,更不能在其他文件使用
int main() {
    
    

	num2 = 100;
	printf("num2 = %d\n", num2);

	return 0;
}

ここに画像の説明を挿入します

スコープ: ローカル変数 (通常のローカル変数と静的ローカル変数) は{} のスコープ内にあります。通常のグローバル変数はプロジェクト全体にスコープがあります。 static 現在のファイルにグローバルに影響を与えます。

ライフ サイクル: 変数定義の実行時に通常のローカル変数のみが開かれ、関数の終了後に解放されます。他の変数は、main 関数 スペースは解放されており、プログラムが終了するまで解放されません。

初期化された値: 通常のローカルの初期化されていない値のみがランダムで、その他は0です。

5) グローバル変数をファイルに分割する問題

C 言語でのグローバル変数の再定義の欠陥:

#include <stdio.h>

// 全局变量之所以能编译过去,是因为其中有三个默认为声明extern,因为extern可写可不写,所以最好声明加上 extern
int a;
int a;
int a;
int a;

int main() {
    
    

	int b;
	//int b; // error 重定义
	return 0;
}

ここに画像の説明を挿入します

まず、ファイル内のグローバル変数の正しい処理を見てみましょう。

main.c書類

#include <stdio.h>
#include "demo.h"

// 声明全局变量cnum
//extern int cnum;
//extern void cfunc();
int main() {
    
    

	cfunc();
	printf("cnum = %d", cnum);

	return 0;
}

demo.c書類

#include <stdio.h>
// 定义全局变量
int cnum=10;
void cfunc() {
    
    

	cnum = 100;
}

demo.h書類

#pragma once

// 声明全局变量cnum
extern int cnum;
extern void cfunc();

ここに画像の説明を挿入します

次に、demo.h ヘッダー ファイル内のグローバル変数の宣言をコメント アウトし、int cnum; に変更します。この場合、コンパイラはそれをコンパイルできます。定義なのか宣言なのかが不明で、 demo.c ファイル内の int cnum=10; の存在は定義とみなされます。 a> が存在するのと同等です。これはステートメントとして扱われます。つまり、コンパイル中にエラーは報告されません。 関数内に の後には、 main 関数内の include "demo.h"mainint cnum;

ここに画像の説明を挿入します

次に、ヘッダー ファイル demo.h を変更して を定義すると、エラーが報告されます。再定義のため、 ファイルでは、グローバル変数は宣言されるだけで定義はされません。定義は ファイル にのみ配置されます。int cnum;int cnum=20;.h.c

ここに画像の説明を挿入します

6) 変数名の重複の問題

範囲の前提の下で近接原則を考慮すると、次のようになります。

例 1:

a.c書類

#include <stdio.h>

static char* language = "java";

void func3()
{
    
    
	printf("language = %s\n", language);
}

b.c書類

#include <stdio.h>

// 不同作用域可以重名
char* language = "c";

func2()
{
    
    
	printf("language = %s\n", language);

}

int main() 
{
    
    
	func2();
	func3();
	char* language = "c++";
	printf("language = %s\n", language);
	if (1)
	{
    
    
		char* language = "python";
		printf("language = %s\n", language);
	}
	printf("language = %s\n", language);
	return 0;
}

ここに画像の説明を挿入します

例 2:

func1.c書類

int va = 7;
int getG(void)
{
    
    
	int va = 20;
	return va;
}

func2.c書類

static int va = 18;
static int getG(void)
{
    
    
	return va;
}
int getO(void)
{
    
    
	return getG();
}

main.c書類

#include <stdio.h>

extern int va;
extern int getG(void);
extern int getO(void);

int main() 
{
    
    
	printf("va=%d\n", va);
	printf("getO=%d\n", getO());
	printf("getG=%d\n", getG());
	printf("%d", va*getO()*getG());

}

ここに画像の説明を挿入します

7) グローバル関数と静的関数

C 言語では、関数はデフォルトでグローバルです。キーワードstaticを使用して関数を静的として宣言すると、関数は次のように定義されます。 /span> static は、この関数が定義されているファイル内でのみこの関数を使用でき、他のファイルでは呼び出すことができないことを意味し、他のファイルでこの関数を宣言しても無駄になります。

  • 通常の関数をそのまま呼び出すと、プロジェクト全体から呼び出せるグローバル関数になります。
  • 静的関数は、関数の定義時にstaticで変更される関数です。静的関数は、現在のファイル関数によってのみ呼び出すことができます。

グローバル関数:

#include <stdio.h>

static char* language = "java";
// 全局函数
void func3()
{
    
    
	printf("language = %s\n", language);
}

静的関数:

// 静态函数
static void func4()
{
    
    
	printf("language = %s\n", language);
}

静的関数を呼び出す必要がある場合は、静的関数が配置されている .c ファイル内でグローバル関数を定義し、このグローバル関数内で同じファイル内の静的関数を呼び出すことができます。このグローバル関数。

// 静态函数
static void func4()
{
    
    
	printf("language = %s\n", language);
}
// 全局函数
void func5()
{
    
    	// 调用静态函数
	func4();
}

ここに画像の説明を挿入します

知らせ:

  • 異なる関数で同じ変数名を使用することができ、それらは異なるオブジェクトを表し、相互に干渉することなく異なるユニットに割り当てられます。
  • 同じソース ファイル内では、グローバル変数とローカル変数は同じ名前を持つことができますが、ローカル変数の範囲内では、グローバル変数は影響を及ぼしません。
  • すべての関数はデフォルトでグローバルです。つまり、すべての関数が同じ名前を持つことはできませんが、 staitc 関数の場合、スコープはファイル レベルであるため、異なるファイルになります a>static関数名は同じであってもかまいません。

8) まとめ

タイプ 範囲 ライフサイクル
auto変数 一对{ }内 現在の関数
staticローカル変数 一对{} プログラム実施期間全体
extern変数 プログラム全体 プログラム実施期間全体
staticグローバル変数 現行ファイル プログラム実施期間全体
extern関数 プログラム全体 プログラム実施期間全体
static関数 現行ファイル プログラム実施期間全体
register変数 一对{} 現在の関数
グローバル変数 プログラム全体 プログラム実施期間全体

メモリレイアウト

1) メモリパーティション

Cコードは、前処理、コンパイル、アセンブリ、リンクの 4 つのステップを経て、実行可能プログラムを生成します。
Windows では、プログラムは通常の実行可能ファイルです。バイナリ実行可能ファイルの基本的な状況は次のとおりです。

ここに画像の説明を挿入します
ここに画像の説明を挿入します

上の図からわかるように、プログラムが実行される前、つまりプログラムがメモリにロードされる前 、実行可能プログラム内 3 つの情報はコード領域 (text)、データ領域 (data)、および初期化されていないデータ領域 (bss) に分割されています ( databss を直接まとめてスタティック エリアまたはグローバル エリアと呼ぶ人もいます)。

  • コード領域
    には、CPU によって実行される機械命令が格納されます。通常、コード領域は共有可能です (つまり、他の実行プログラムがコード領域を呼び出すことができます)。コード領域を共有可能にする目的は、頻繁に実行されるプログラムの場合、メモリ内に必要なコードのコピーが 1 つだけになるようにすることです。 コード領域は通常、読み取り専用です。コード領域を読み取り専用にする理由は、プログラムが誤って命令を変更するのを防ぐためです。さらに、コード領域には、ローカル変数に関する関連情報も計画されています。

  • グローバル初期化データ領域/静的データ領域 (データセクション)
    この領域には、プログラム内で明示的に初期化されるグローバル変数および初期化された静的変数 (グローバル静的変数とローカル静的変数を含む) が含まれます。変数)と定数データ(文字列定数など)。

  • 初期化されていないデータ領域 (bss 領域とも呼ばれます)
    には、初期化されていないグローバル変数と初期化されていない静的変数が保存されます。未初期化データ領域内のデータは、プログラムの実行が開始される前に、カーネルによって 0 または空 (NULL) に初期化されます。

    プログラムがメモリにロードされる前に、 コード領域とグローバル領域 (data および bss) のサイズは固定されています . プログラムの実行中は変更できません。次に、実行可能プログラムを実行すると、システムはプログラムをメモリにロードします。 コード領域 (text)、データ領域 (data)、および初期化されていないデータ領域 (bss) が次のように分割されます。実行可能プログラムの情報)にスタック領域とヒープ領域も追加されます。

    ここに画像の説明を挿入します

  • コード領域 (テキスト セグメント)
    は、実行可能ファイルのコード セグメントをロードします。すべての実行可能コードはコード領域にロードされます。このメモリは操作中に変更できません。

  • 初期化されていないデータ領域 (BSS)
    は、実行可能ファイルBSS セクションをロードします。場所はデータ セクションから離れていても近くても構いません。保存されます。データ セグメント内のデータ (初期化されていないグローバル データ、初期化されていない静的データ) のライフ サイクルは、プログラム実行プロセス全体です。

  • グローバル初期化データ領域/静的データ領域 (データセグメント)
    実行可能ファイルのデータセグメントがロードされ、データセグメント (グローバル初期化、静的初期化データ、リテラル定数 (読み取り専用)) のデータの有効期間は、プログラム実行プロセス全体です。

  • スタック領域 (スタック)
    スタックは先入れ後出しのメモリ構造であり、関数のパラメータ値、戻り値、ローカル変数など。ローカル変数はプログラム実行中にリアルタイムでロードと解放が行われるため、スタック領域の申請と解放がローカル変数のライフサイクルとなります。

  • ヒープ領域 (ヒープ)
    ヒープは大きなコンテナです。その容量はスタックの容量よりもはるかに大きくなりますが、先入れ後出しの機能はありません。スタックのように順序付けします。動的メモリ割り当てに使用されます。ヒープは、メモリ内の BSS 領域とスタック領域の間に配置されます。通常、プログラマによって割り当ておよび解放されますが、プログラマが解放しない場合は、プログラム終了時にオペレーティング システムによってリサイクルされます。

2) ストレージの種類の概要

タイプ 範囲 ライフサイクル ストレージの場所
auto変数 一对{} 現在の関数 スタック領域
staticローカル変数 一对{} プログラム実施期間全体 data セクションで初期化され、 BSS セクション では初期化されません。
extern変数 プログラム全体 プログラム実施期間全体 data セクションで初期化され、 BSS セクション では初期化されません。
staticグローバル変数 現行ファイル プログラム実施期間全体 data セクションで初期化され、 BSS セクション では初期化されません。
extern関数 プログラム全体 プログラム実施期間全体 コードエリア
static関数 現行ファイル プログラム実施期間全体 コードエリア
register変数 一对{} 現在の関数 CPU実行時にレジスタに保存されます
文字列定数 現行ファイル プログラム実施期間全体 data一部

例:

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

int e;
static int f;
int g = 10;
static int h = 10;
int main()
{
    
    
	int a;
	int b = 10;
	static int c;
	static int d = 10;
	char *i = "test";
	char *k = NULL;

	printf("&a\t %p\t //局部未初始化变量\n", &a);
	printf("&b\t %p\t //局部初始化变量\n", &b);

	printf("&c\t %p\t //静态局部未初始化变量\n", &c);
	printf("&d\t %p\t //静态局部初始化变量\n", &d);

	printf("&e\t %p\t //全局未初始化变量\n", &e);
	printf("&f\t %p\t //全局静态未初始化变量\n", &f);

	printf("&g\t %p\t //全局初始化变量\n", &g);
	printf("&h\t %p\t //全局静态初始化变量\n", &h);

	printf("i\t %p\t //只读数据(文字常量区)\n", i);

	k = (char *)malloc(10);
	printf("k\t %p\t //动态分配的内存\n", k);

	return 0;
}

ここに画像の説明を挿入します

输出结果
&a       003DFE9C        //局部未初始化变量
&b       003DFE90        //局部初始化变量
&c       00088180        //静态局部未初始化变量
&d       00088014        //静态局部初始化变量
&e       000884A4        //全局未初始化变量
&f       0008817C        //全局静态未初始化变量
&g       0008800C        //全局初始化变量
&h       00088010        //全局静态初始化变量
i        00085060        //只读数据(文字常量区)
k        007104D0        //动态分配的内存

3) メモリ操作機能

3.1 memset()

メモリ空間を特定の値で埋めます。

  • ヘッダーファイル:#include <string.h>
  • 関数を定義します。void *memset(void *s, int c, size_t n);
  • 機能: s のメモリ領域の最初n バイトにパラメータを入力しますc
  • パラメータ:
    s:操作対象のメモリの最初のアドレス入力された文字、< /span> です。設定する必要があるサイズ である必要があり、範囲は ですが、パラメータは s
    c:cintunsigned char0~255
    n:
  • 戻り値:s の最初のアドレス

例:

#include <stdio.h>
#include <string.h>

// memset函数
int main()
{
    
    
	int a = 10;
	//a = 0;  -> memset()
	memset(&a, 0, sizeof(a));
	printf("a=%d\n", a);

	char str[20] = "hellocdtaogang";
	printf("str=[%s]\n", str);
	memset(str, 0, sizeof(str));
	printf("str=[%s]\n", str);
	// 将前10个字符置为a字符
	memset(str, 'a', sizeof(str) - 10);  // memset(str, 97, sizeof(str) - 10);
	printf("str=[%s]\n", str);
	return 0;
}
输出结果
a=0
str=[hellocdtaogang]
str=[]
str=[aaaaaaaaaa]

3.2 memcpy()

メモリ内容をコピーする

  • ヘッダーファイル:#include <string.h>
  • 関数を定義します。void *memcpy(void *dest, const void *src, size_t n);
  • 機能: src が指すメモリの最初のnバイトを、dest が指すメモリにコピーします。住所・アドレス。
  • パラメータ:
    dest:宛先メモリの最初のアドレス
    src:ソース メモリの最初のアドレス、注:< a i=4> と が指すメモリ空間は重複できません。重複すると、プログラムがエラーを報告する可能性があります。コピーされるバイト数destsrc
    n:
  • 戻り値:dest の最初のアドレス

例:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>

// memcpy函数
int main()
{
    
    
	int a[10] = {
    
     1,2,3,4,5,6,7,8,9,10 };
	int b[10] = {
    
     0 };
	// a = b;// error 常量不能修改
	// 将a数组中的前五个元素拷贝到b数组中
	memcpy(b, a, sizeof(int)*5);
	for (int i = 0; i < sizeof(b)/sizeof(b[0]); i++)
	{
    
    
		printf("%d ", b[i]);
	}
	printf("\n");
	char str1[128] = "";
	char str2[128] = "abc\0def\0hellocdtaogang";
	char str3[128] = "";
	// 使用strncpy
	strncpy(str1, str2, sizeof(char) * 22);
	for (int i = 0; i < 22; i++)
	{
    
    
		printf("%d ", str1[i]);
	}
	printf("\n");
	// 使用memcpy
	memcpy(str3, str2, sizeof(char) * 22);
	for (int i = 0; i < 22; i++)
	{
    
    
		printf("%d ", str3[i]);
	}
	return 0;
}
输出结果
1 2 3 4 5 0 0 0 0 0
97 98 99 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
97 98 99 0 100 101 102 0 104 101 108 108 111 99 100 116 97 111 103 97 110 103

3.3 memmove()

memmove() 関数の使用法は memcpy() と同じですが、相違点は、 dest は処理できますが、実行効率は よりも低くなります。 srcmemmove()memcpy()

3.4 memcmp()

メモリ内容を比較する

  • ヘッダーファイル:#include <string.h>
  • 関数を定義します。int memcmp(const void *s1, const void *s2, size_t n);
  • 機能:s1s2 が指すメモリ領域の最初のnバイトを比較します。
  • パラメータ:
    s1:メモリの最初のアドレス 1
    s2:メモリの最初のアドレス 2
    n:比較される最初の n バイト < /span>
  • 返回值:
    相等:=0
    大于:>0
    小于:<0

例:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>

// memcmp函数
int main()
{
    
    
	int a[10] = {
    
     1, 0, 2, 3, 5, 6, 2, 8, 9, 10 };
	int b[10] = {
    
     1, 0, 4, 4, 5, 6, 7, 8, 9, 10 };

	int res1 = memcmp(a, b, sizeof(int));
	printf("res1 = %d\n", res1);
	
	int res2 = memcmp(a, b, sizeof(int)*10);
	printf("res2 = %d\n", res2);

	int res3 = strncmp(a, b, sizeof(int) * 10);  // strncmp遇到0就结束了
	printf("res3 = %d\n", res3);

	char str1[] = "abcd\0abc";
	char str2[] = "abcd\0bbc";
	printf("%d\n", strncmp(str1, str2, sizeof(str1))); // strncmp遇到\0就结束了
	printf("%d\n", memcmp(str1, str2, sizeof(str1))); 

	return 0;
}
输出结果
res1 = 0
res2 = -1
res3 = 0
0
-1

4) ヒープ領域のメモリ割り当てと解放

4.1 malloc()

メモリ空間を構成する

  • ヘッダーファイル:#include <stdlib.h>
  • 関数を定義します。void *malloc(size_t size);
  • 機能: 型指定子で指定された型を格納するために、メモリの動的記憶領域(ヒープ領域)にsizeバイトの連続領域を確保します。割り当てられたメモリ空間の内容は不確実であり、通常は memset を使用して初期化されます。
  • パラメータ:
    size: 割り当てられるメモリ サイズ (単位: バイト)
  • 戻り値:
    成功: 割り当てられたスペースの開始アドレス
    失敗:NULL

例:

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

// malloc函数
int main()
{
    
    	
	//int a[10]; 直接是从栈区申请空间
	//申请一个数组,数组有10个元素,每个元素int类型,到堆区申请内存空间
	int* p = (int *)malloc(sizeof(int) * 10);  // malloc函数的返回值未void *类型最好是强转下
	*p = 100;
	*(p + 5) = 200;
	for (int i = 0; i < 10; i++)
	{
    
    
		printf("%d ", *(p + i));
	}
}

ここに画像の説明を挿入します

输出结果
100 -842150451 -842150451 -842150451 -842150451 200 -842150451 -842150451 -842150451 -842150451

4.2 無料()

元々設定されていたメモリを解放します

  • ヘッダーファイル:#include <stdlib.h>
  • 関数を定義します。void free(void *ptr);
  • 機能: ptr が指すメモリ空間を解放します。ptr は、解放された領域の最初のアドレスを指す任意の型のポインタ変数です。 。同じメモリ空間を複数回解放するとエラーが発生します。
  • パラメータ:
    ptr:解放される領域の最初のアドレス。解放される領域は、malloc 関数によって割り当てられた領域である必要があります。
    戻り値: なし

例:

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

// free函数
int main()
{
    
    
	//申请一个字符数组,有1024元素
	char* p = (char *)malloc(1024);
	//将申请到空间清0
	memset(p, 0, 1024);
	strcpy(p, "hellocdtaogang");
	// 释放内存
	free(p);
	//free(p+1) // free 参数  地址必须是上一次malloc申请过的,不能去改变这个地址
	//printf("%s\n", p); // 释放完了再打印那么就不会是你想要的数据
	//free(p); // malloc申请的空间不可以释放两次,申请一次,释放一次
	return 0;
}

注:free最後に適用されたスペースは 1 回だけ解放できます。freeパラメータ アドレスは最後のアドレスである必要があります申請したスペースを 2 回解放することはできません。1 回申請して 1 回解放します。 mallocmalloc

4.3 メモリリーク

メモリ リーク: 適用されるだけで解放されない
メモリ汚染: 未適用のメモリ領域にデータを書き込む

ここに画像の説明を挿入します

メモリパーティションコード解析

1) スタック領域アドレスを返す

例:通常のローカル変数はスタック領域に存在し、関数呼び出しが完了すると解放されるため、通常のローカル変数のアドレスを操作のために返すことはできません。

#include <stdio.h>

int* newfunc() 
{
    
    

	int a = 10; 
	a *= 10;

	return &a; // 函数调用完毕,a释放

}

int main()
{
    
    

	int* p = newfunc();
	//操作野指针指向的内存
	*p = 200; // error p所指向的空间已经被释放,
	printf("%d\n", *p);

	return 0;
}

ここに画像の説明を挿入します

2) データ領域アドレスを返す

例 1:静的ローカル変数は静的グローバル領域に存在し、初期化されたローカル変数はデータ領域に存在します。プログラムが終了しない限り解放されません。 , したがって、これらの変数のアドレスを返すことができます。

#include <stdio.h>

int* newfunc() 
{
    
    

	//int a = 10; 
	static int a = 10;
	a *= 10;

	return &a; 

}

int main()
{
    
    

	int* p = newfunc();
	*p = 200; // p所指向的空间没有释放(静态局部变量),则可以操作这块内存
	printf("%d\n", *p);

	return 0;
}

ここに画像の説明を挿入します
例 2:静的グローバル変数とグローバル変数は静的グローバル領域に存在し、初期化された変数はデータ領域に存在します。プログラムが終了しない限り、それらは存在しません。解放されるので、これらの変数のアドレスは操作を返すことができます。

#include <stdio.h>

//int a = 10; // 全局变量变量
static int a = 10; // 静态全局变量
int* newfunc() 
{
    
    
	//int a = 10; 
	//static int a = 10;
	a *= 10;

	return &a;

}

int main()
{
    
    
	int* p = newfunc();
	*p = 200; // p所指向的空间没有释放(静态全局变量、全局变量),则可以操作这块内存
	printf("%d\n", *p);

	return 0;
}

要するに:通常のローカル変数は関数終了後に解放されるのに対し、静的ローカル変数、グローバル変数、静的グローバル変数は解放されるため、通常のローカル変数のアドレスのみを返すことはできません。変数 これらの変数はプログラムが終了しない限り解放されないため、これらの変数のアドレスを操作に返すことができます。

3) 価値の移転

例 1:仮パラメータの本質はローカル変数です。関数呼び出しが完了すると解放されるため、仮パラメータのアドレスを返すことはできません。

#include <stdio.h>

int* newfunc2(int num)

{
    
    	//形参的本质就是局部变量
	num += 100;

	return &num; // 函数调用完毕,num释放,不可以返回形参的地址

}
int main()
{
    
    
	int num = 10;
	int *p = newfunc2(num); 
	*p = 200;


	return 0;
}

例 2:k 個の実パラメータのアドレスを返す関数を定義します。これにより、関数の終了時に実パラメータのアドレスが解放されなくなります。

#include <stdio.h>

int* newfunc3(int *k)

{
    
    
	int i = 100;
	*k = *k + i;

	return k; // 返回k指向的num的地址,所以函数结束num变量的地址没有被释放
}
int main()
{
    
    
	int num = 10;
	int* p = newfunc3(&num);

	return 0;
}

ここに画像の説明を挿入します

例 3:

4) ヒープ領域アドレスを返す

例:ヒープ領域のアドレスを返すことができ、関数終了後にヒープ領域は解放されません。ただし、文字列定数を直接割り当てることはできません。スペースの損失とメモリの損失が発生します。リーク

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

char* newfunc4()
{
    
    
	char* q = malloc(100);

	return q; // 堆区的地址是可以返回的,函数结束不会被释放
}
int main()
{
    
    
	char* p = newfunc4();
	p = "hello";
	free(p); // error p并没有指向堆区,而是指向文字常量区"hello"

	return 0;
}

ここに画像の説明を挿入します
ここに画像の説明を挿入します

p を整数に代入

ここに画像の説明を挿入します

使用strcpy(p, "hello");してもメモリ リークは発生しません

ここに画像の説明を挿入します

ここに画像の説明を挿入します

5) 実パラメータは第 1 レベルのポインタのアドレスです。

例 1: 関数の値渡しでは実際のパラメータの値を変更することはできません

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void mem_p(char *q) 
{
    
    
	q = malloc(1024);
	return;
}
int main()
{
    
    
	char* p = NULL;
	mem_p(p);
	strcpy(p, "hello");
	printf("p=%s\n", p);

	return 0;
}

ここに画像の説明を挿入します

直接コンパイルして実行すると、エラーは報告されませんが、データは出力されません。コンパイラはエラーを表示しないとしか言​​えませんが、エラーはブレークポイントを通じて確認できます。

ここに画像の説明を挿入します

が上記のエラーを引き起こすのは、 pNULL を指しており、ヒープ アドレスを指していないため、メモリ汚染が発生するためです。解決策は です。 p はヒープ領域に開かれたアドレスを指すだけです。

char* mem_p(char *q) 
{
    
    
	q = malloc(1024);
	return q;
}
int main()
{
    
    
	char* p = NULL;
	p = mem_p(p);
	strcpy(p, "hello");
	printf("p=%s\n", p);

	return 0;
}

ここに画像の説明を挿入します

例 2: 実パラメータのアドレスを渡すと、関数呼び出し時に実パラメータの値を変更できます(実パラメータは第 1 レベルのポインタ アドレスです) 、仮パラメータは第 2 レベルのポインタです)

void mem_p2(char** k)
{
    
    
	*k = malloc(1024);
	return;
}
int main()
{
    
    
	char* p = NULL;
	mem_p2(&p);
	strcpy(p, "hello");
	printf("p=%s\n", p);

	return 0;
}

ここに画像の説明を挿入します

ここに画像の説明を挿入します

おすすめ

転載: blog.csdn.net/qq_41782425/article/details/127956346