C语言学习之路(基础篇)—— 内存管理

news2024/11/17 3:32:01

说明:该篇博客是博主一字一码编写的,实属不易,请尊重原创,谢谢大家!

作用域

C语言变量的作用域分为:

  • 代码块作用域(代码块是{}之间的一段代码)
  • 函数作用域
  • 文件作用域

1) 局部变量

生命周期:什么时候开辟空间(出生),释放空间(死亡),这个过程叫生命周期。
局部变量也叫auto自动变量(auto可写可不写),一般情况下代码块{}内部定义的变量都是自动变量,它有如下特点:

  • 作用域:在一个函数内定义,只在函数范围内有效;在复合语句中定义,只在复合语句中有效
  • 生命周期:程序运行至变量定义处开辟空间,随着函数调用的结束或复合语句的结束,局部变量的生命周期也结束了,释放空间
  • 如果没有赋初值,内容为随机

示例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局部变量的作用域也是在定义的函数内有效
  • 生命周期:static局部变量的生命周期和程序运行周期一样,在执行main函数之前就已经开辟空间,当程序结束之后才释放空间;同时static局部变量的值只初始化一次,但可以赋值多次
  • static局部变量若未赋以初值,则由系统自动赋值:数值型变量自动赋初值0,字符型变量赋空字符

示例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,字符型变量赋空字符
  • 不同文件的全局变量不可重名

示例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)全局变量

  • 作用域:在函数外定义,作用范围被限制在所定义的文件中(当前文件);不同文件静态全局变量可以重名,但作用域不冲突
  • 生命周期:static全局变量的生命周期和程序运行周期一样,执行main函数之前就已经开辟空间,程序结束之后才释放空间;同时staitc全局变量的值只初始化一次
  • static全局变量若未赋以初值,则由系统自动赋值:数值型变量自动赋初值0,字符型变量赋空字符

示例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;
}

在这里插入图片描述

作用域: 局部变量(普通局部和静态局部)在{}范围之内;普通全局变量作用域在整个工程;静态全局作用当前文件。

生命周期: 只有普通局部变量是运行至变量定义处时开辟,函数结束释放,其他变量都是执行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;会当做定义,那么在main函数中include "demo.h"后,相当于在main函数中存在int 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可以将函数声明为静态,函数定义为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函数,那么作用域是文件级的,所以不同的文件static函数名是可以相同的。

8) 小结

类型作用域生命周期
auto变量一对{}内当前函数
static局部变量一对{}整个程序运行期
extern变量整个程序整个程序运行期
static全局变量当前文件整个程序运行期
extern函数整个程序整个程序运行期
static函数当前文件整个程序运行期
register变量一对{}当前函数
全局变量整个程序整个程序运行期

内存布局

1) 内存分区

C代码经过预处理、编译、汇编、链接4步后生成一个可执行程序。
Windows 下,程序是一个普通的可执行文件,以下列出一个二进制可执行文件的基本情况:

在这里插入图片描述
在这里插入图片描述

通过上图可以得知,在没有运行程序前,也就是说程序没有加载到内存前,可执行程序内部已经分好3段信息,分别为代码区(text)、数据区(data)和未初始化数据区(bss)3 个部分(有些人直接把databss合起来叫做静态区或全局区)。

  • 代码区
    存放 CPU 执行的机器指令。通常代码区是可共享的(即另外的执行程序可以调用它),使其可共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可。代码区通常是只读的,使其只读的原因是防止程序意外地修改了它的指令。另外,代码区还规划了局部变量的相关信息。

  • 全局初始化数据区/静态数据区(data段)
    该区包含了在程序中明确被初始化的全局变量、已经初始化的静态变量(包括全局静态变量和局部静态变量)和常量数据(如字符串常量)。

  • 未初始化数据区(又叫 bss 区)
    存入的是全局未初始化变量和未初始化静态变量。未初始化数据区的数据在程序开始执行之前被内核初始化为 0 或者空(NULL)。

    程序在加载到内存前,代码区和全局区(data和bss)的大小就是固定的,程序运行期间不能改变。然后,运行可执行程序,系统把程序加载到内存,除了根据可执行程序的信息分出代码区(text)、数据区(data)和未初始化数据区(bss)之外,还额外增加了栈区、堆区。

    在这里插入图片描述

  • 代码区(text segment)
    加载的是可执行文件代码段,所有的可执行代码都加载到代码区,这块内存是不可以在运行期间修改的。

  • 未初始化数据区(BSS)
    加载的是可执行文件BSS段,位置可以分开亦可以紧靠数据段,存储于数据段的数据(全局未初始化,静态未初始化数据)的生存周期为整个程序运行过程。

  • 全局初始化数据区/静态数据区(data segment)
    加载的是可执行文件数据段,存储于数据段(全局初始化,静态初始化数据,文字常量(只读))的数据的生存周期为整个程序运行过程。

  • 栈区(stack)
    栈是一种先进后出的内存结构,由编译器自动分配释放,存放函数的参数值、返回值、局部变量等。在程序运行过程中实时加载和释放,因此,局部变量的生存周期为申请到释放该段栈空间。

  • 堆区(heap)
    堆是一个大容器,它的容量要远远大于栈,但没有栈那样先进后出的顺序。用于动态内存分配。堆在内存中位于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:需要操作内存s的首地址
    c:填充的字符,c虽然参数为int,但必须是unsigned char ,范围为0~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:源内存首地址,注意: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()一样,区别在于:destsrc所指的内存空间重叠时,memmove()仍然能处理,不过执行效率比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个字节
  • 返回值:
    相等:=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 free()

释放原先配置的内存

  • 表头文件:#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只能释放一次上次申请过的空间;free参数地址必须是上一次malloc申请过的,不能改变这个地址;malloc申请的空间不可以释放两次,申请一次,释放一次。

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) 返回data区地址

示例1:静态局部变量存在静态全局区,已初始化的存在data区,只要程序不退出,就不会释放,所以这些变量的地址是可以返回操作。

#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:静态全局、全局变量存在静态全局区,已初始化的存在data区,只要程序不退出,就不会释放,所以这些变量的地址是可以返回操作。

#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: 通过函数的值传递,不能改变实参的值

#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;
}

在这里插入图片描述

如果直接编译运行,不会报错,但是没有打印数据,只能说编译器没有提示报错,但是可以通过打断点的方式来查看错误

在这里插入图片描述

导致以上错误就是p指向的是NULL,并没有指向堆区地址,就造成了内存污染,解决方法就是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: 传实参的地址,可以在调用函数改变实参的值(实参为一级指针地址,形参为二级指针)

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;
}

在这里插入图片描述

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/30314.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python 基础(一):初识 Python

文章目录Python是什么解释型语言Python 之父Python 名字的由来Python 的应用领域人生苦短&#xff0c;我用 Python大家好&#xff0c;我是水滴~~ 本文对 Python 做了一个初步的介绍&#xff0c;并了解 Python 的作者、名字由来、应用领域等。 Python是什么 Python 是一种面向…

什么是软件测试?

什么是软件测试&#xff1f; 软件测试的定义&#xff1a;在一定条件下对软件进行操作&#xff0c;发现软件的问题&#xff0c;提高软件的质量。 软件测试在开发中的有着重要地位。软件测试在各阶段的完成相应的任务&#xff0c;需求测试&#xff0c;架构测试&#xff0c;详细测…

C语言条件运算符——三元表达式例题(素材来自C技能树)

&#x1f4d1;三目运算符 三目运算符也叫条件运算符、三元运算符&#xff0c;是由一个问号和一个冒号组成。语法&#xff1a;表达式1?表达式2:表达式3;语义&#xff1a;先执行表达式1&#xff0c;如果表达式1的结果如果为真&#xff0c;那么执行表达式2&#xff0c;并且这个整…

level2接口有什么用?是如何获取A股行情数据的?

目前国内有很多数据团队专门为金融机构、学术团体和量化研究者们提供的本地量化金融数据服务&#xff0c;那么最常见的就是通达信、同花顺等团队&#xff0c;他们开发出来的level2接口可快速查看和计算金融数据&#xff0c;无障碍解决本地、Web、金融终端调用数据的需求。为了满…

彻底解决 K8s 节点本地存储被撑爆的问题

一、存储的内容 要解决存储使用过多的问题&#xff0c;就得先了解存储中都保存了些什么内容&#xff0c;否则解决不了问题&#xff0c;还可能带来更多的风险。 1.1、镜像 容器要在节点上运行&#xff0c;kubelet 首先要拉取容器镜像到节点本地&#xff0c;然后再根据镜像创建…

3分钟读懂RD与RT

Route-Distinguisher&#xff08;后简称"RD"&#xff09;&#xff0c;Route-Target&#xff08;后简称"RT"&#xff09;经常出现在EVPN、MPLS VPN中&#xff0c;但它们是完全不同的两个概念&#xff0c;初学者往往难以区分两者的差异。学霸题&#xff1a;区…

Jmeter入门

性能测试&#xff1a;模拟多个用户的操作对服务器硬件性能的影响 TPS&#xff1a;Transaction per Second&#xff0c;每秒事务处理能力 RT&#xff1a;Response Time&#xff0c;响应时间 安装 由于本人只有window系统&#xff0c;故只讲解win下的安装 安装JDK 下载地址&a…

2023年最热门的网络安全岗位分析

大数据、人工智能、云计算、物联网、5G等新兴技术的高速发展&#xff0c;蒸蒸日上。但是随之也出现了许多问题&#xff0c;比如&#xff1a;政府单位、企业、个人信息泄露&#xff0c;网络安全问题日益严峻&#xff0c;网络空间安全建设刻不容缓。 网络安全人才需求量巨大&…

双核驱动,合力共进,郁锦香与凯里亚德酒店强强联合释放多元化商业价值

近日&#xff0c;以“清风雅茗 亨嘉之会”为主题的2022锦江酒店&#xff08;中国区&#xff09;厦门站品牌投资品鉴会圆满落幕&#xff0c;众多投资人和酒店品牌方负责人齐聚一堂&#xff0c;在充满文艺气息的滨海城市厦门&#xff0c;感受精致、愉悦的慢生活。在品牌见面环节&…

NodeJs实战-Express构建照片存储网站(1)-ejs视图引擎填充数据

ejs视图引擎填充数据express 生成项目安装 express-generator生成项目程序结构理解项目结构生成的文件的含义视图渲染填充照片数据增加路由器修改 app.js修改 routes增加对应的视图页面路由器 res.render 查找视图逻辑新增文件之后的项目结构图效果图项目地址express 生成项目 …

kali Linux常用快捷键及vim的基本使用

kali Linux 系统快捷键 Ctrl Alt T &#xff1a;打开一个新的命令行终端。 如果是在桌面打开的是这种情况 Ctrl C 复制。 Ctrl Z 撤消。 Ctrl S &#xff1a;保存 Ctrl Q &#xff1a;退出。 终端快捷键 TAB &#xff1a;补全命令。 Ctrl &#xff1a;放大文字…

Netty系列(一):Springboot整合Netty,自定义协议实现

Netty是由JBOSS提供的一个java开源框架&#xff0c;现为 Github上的独立项目。Netty提供异步的、事件驱动的网络应用程序框架和工具&#xff0c;用以快速开发高性能、高可靠性的网络服务器和客户端程序。 也就是说&#xff0c;Netty 是一个基于NIO的客户、服务器端的编程框架&…

目标检测论文解读复现之十六:基于改进YOLOv5的小目标检测算法

前言 此前出了目标改进算法专栏&#xff0c;但是对于应用于什么场景&#xff0c;需要什么改进方法对应与自己的应用场景有效果&#xff0c;并且多少改进点能发什么水平的文章&#xff0c;为解决大家的困惑&#xff0c;此系列文章旨在给大家解读最新目标检测算法论文&#xff0c…

Java项目使用intellij-IDEA查看依赖包版本是否有冲突(方法及工具)

编译器及版本idea-ultimate依赖管理工具maven 第一个是idea本身的 Step1&#xff1a;点击右侧的maven Step2&#xff1a;右键依赖项&#xff0c;点击分析依赖关系 Step3&#xff1a;可以在模块名位置进行切换&#xff0c;左侧三角的标志则表示该包引入了多个版本&#xff…

【云原生】Docker网络原理及Cgroup硬件资源占用控制

内容预知 1.dockers的网络模式 获取容器的进程号 docker网络模式的特性 1.1 host主机模式 1.2 container模式 1.3 none模式 1.4 bridge 桥接模式 1.5 容器的自定义网络 &#xff08;1&#xff09;未创建自定义网络时&#xff0c;创建指定IP容器的测试 &#xff08;2&a…

双坐标轴柱状图

双坐标轴柱状图 setwd(“H:/分析评价 20220531/6-分析过程”) #设置工作路径 library(xlsx)#加载excel文件包 #---------------------------------------------------------------------------------------------------------- tiff(file“1-占比.tiff”,res600,width6000,hei…

负载均衡有哪些?

目录 【一】前言 【二】负载均衡分类 2.1 DNS 2.2 硬件负载均衡 2.3 软件负载均衡 2.4 组合负载均衡 【三】负载均衡算法 3.1 负载均衡算法分类 3.2 轮询 3.3 加权轮询 3.4 负载最低优先 3.5 性能最优类 3.6 Hash 【四】总结 【一】前言 在互联网尤其是移动互联…

【前沿技术RPA】 一文了解UiPath的项目活动设置

&#x1f40b;作者简介&#xff1a;博主是一位.Net开发者&#xff0c;同时也是RPA和低代码平台的践行者。 &#x1f42c;个人主页&#xff1a;会敲键盘的肘子 &#x1f430;系列专栏&#xff1a;UiPath &#x1f980;专栏简介&#xff1a;UiPath在传统的RPA&#xff08;Robotic…

【计算机组成原理Note】5.2 指令周期的数据流

5.2 指令周期的数据流 5.2.1 指令周期 指令周期&#xff1a;CPU从主存中每取出并执行一条指令所需的全部时间。 指令周期&#xff1a;常常用若干机器周期来表示&#xff0c;机器周期又叫CPU周期。 一个机器周期又包含若干时钟周期(也称为节拍、T周期或CPU时钟周期&#xff…

SpringCloud微服务(十一)——Sentinel服务熔断限流

SpringCloud Alibaba Sentinel服务熔断与限流 简介 github&#xff1a;[https://github.com/alibaba/Sentinel/wiki/%E4%B8%BB%E9%A1%B5](https://github.com/alibaba/Sentinel/wiki/如何使用) 官网&#xff1a;https://spring-cloud-alibaba-group.github.io/github-pages/…