传值、传址、空间释放详细图解

2024-03-19 00:28

本文主要是介绍传值、传址、空间释放详细图解,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

前言

一.进程

1.1 进程的映射

1.2 进程的虚拟空间

二.函数传参

2.1 函数传参

2.2 函数传值

2.2.1 函数传值案例1

2.2.2 函数传值案例2

2.2.3 返回值为常量

2.3 函数传送地址

2.3  字符串使用


前言

详细介绍函数传值和传地址区别:进行数据操作的区别,函数传值为临时拷贝,不会对实参影响。而传地址时,就是传实参,是可以影响到实参的..........

一.进程

1.1 进程的映射

物理空间映射到虚拟空间是计算机内存管理中的一个重要概念,它允许程序使用的地址(虚拟地址)与计算机物理内存的地址(物理地址)之间进行转换

这种映射机制主要涉及以下几个关键点:

  1. 虚拟地址与物理地址:用户在编程时使用的地址称为虚拟地址或逻辑地址,而计算机物理内存中的地址称为物理地址。虚拟地址通过内存管理单元(MMU)的转换,映射到物理内存的实际位置。
  2. 虚拟存储空间与物理存储空间:虚拟地址对应的存储空间称为虚拟存储空间或逻辑地址空间,而物理地址对应的存储空间称为物理存储空间或主存空间。
  3. 虚拟页与物理页:在虚拟内存系统中,虚拟页是虚拟地址空间的一部分,它们会映射到物理内存中的物理页。这种分页机制使得程序可以拥有比实际物理内存更大的地址空间。
  4. 固定映射区与动态映射区:在固定映射区中,虚拟内存地址固定映射到物理内存的高端地址上,而在动态映射区和永久映射区中,虚拟地址不是固定的,可以被动态改变以映射到不同的物理地址上。
  5. 内存映射mmap:内存映射是一种将文件或其他资源映射到进程的虚拟地址空间的技术,这样进程就可以像访问内存一样访问这些资源。这通常用于优化文件I/O操作,因为它减少了数据从磁盘到内存的复制次数。

1.2 进程的虚拟空间

进程的虚拟空间是一个逻辑意义上的内存空间概念,它允许每个进程拥有独立的地址空间,使得程序的运行更加安全和高效

二.函数传参

2.1 函数传参

函数传参时可以通过传递地址的方式来实现对实际参数的修改。具体来说:

  1. 指针传递:在函数定义中,参数使用指针类型,调用函数时传递变量的地址。在函数内部,通过解引用操作可以访问和修改该地址指向的数据。这样,当函数返回时,原变量的值可能会被改变。
  2. 数组传参:在C语言中,当数组作为函数参数时,实际上传递的是数组首元素的地址。这是因为数组在内存中是连续存储的,所以传递首元素地址足以让函数访问整个数组。
  3. 引用传递:在某些编程语言(如C++)中,可以使用引用传递的方式。引用本质上是一个别名,它允许函数直接操作传递给它的变量。在函数内部对引用参数的任何修改都会反映到原始变量上。
  4. const修饰符:如果形参是指针并且加了const修饰,则该指针指向的数据是不可修改的。这有助于保护数据不被意外修改。
  5. 值传递:与地址传递相对的是值传递,值传递会创建一个实参的副本,函数内部的操作不会影响原始变量。但对于数组,由于发生了“降维”,实际上传入的是数组首元素的地址,因此即使是值传递,函数内部对数组元素的修改也是真实存在的。

总之,在实际应用中,选择哪种传递方式取决于具体的编程需求。例如,如果需要在函数内部修改外部变量的值,通常会选择指针传递或引用传递。而如果只是需要读取数据而不做修改,可能会选择值传递。

2.2 函数传值

2.2.1 函数传值案例1

这是最常见的传参方式,当调用函数时,实参的值被复制给形参,形参和实参分别占用不同的存储空间。在函数内部对形参的修改不会影响到外部的实参。这种方式简单且安全,但可能会因为参数复制而降低效率。

 #include<stdio.h>#include <stdlib.h>#include <string.h>void GetMemory1(char *p) // p ==== NULL{*p = (char *)malloc(100); // 向系统申请100个字节的堆空间, 让p 指向该区域}void Test1(void){char *str = NULL;GetMemory1(str); /// str ==== NULL// 经过GetMemory1 的操作后 str的指向依然没有变换还是指向NULLstrcpy(str, "hello world"); // 提示: 拷贝字符串, 把"hello world" 拷贝到str 所指向的内存空间中// 拷贝函数出现段错误printf("%s\n" , str);}int main(int argc, char const *argv[]){Test1();return 0;}

函数传值进行操作内存时,需要注意,如下:调用开辟内存函数时,没有传入地址,会出现野指针问题。这样传值就是错误的,正确的写法写在后面传地址案例中。

2.2.2 函数传值案例2

这里参考使用static数据段的操作

#include<stdio.h>#include <stdlib.h>#include <string.h>
char *GetMemory2(void)
{// 数组 p 所存放的位置为 栈空间, 当函数 GetMemory2 退出返回时, 该区域会被系统回收// 不应该返回该内存中的地址// 可以使用 static 来修饰该数组, 使其的内存区域改为数据段static char p[] = "hello world";return p;
}void Test2(void){char *str = NULL;str = GetMemory2();printf("TEST‐2:%s\n",str);}
int main(int argc, char const *argv[])
{Test2();return 0;
}

这里如果不使用static,程序会报错,因为它是传送值的

2.2.3 返回值为常量

这里的函数调用的返回值为常量,也就是存储在虚拟空间数据段

 #include<stdio.h>
#include <stdlib.h>
#include <string.h>
// TEST3
char *GetMemory3(void)
{// 直接返回 常量区的内存地址 , 注意该区域只读return "hello world";
}
void Test3(void)
{char *str = NULL;str = GetMemory3();printf("TEST‐3:%s\n",str);}int main(int argc, char const *argv[])
{Test3();return 0;
}

2.3 函数传送地址

在这种传参方式中,实参的地址被传递给形参,形参通常是指针类型。函数内部通过指针来访问和修改实参的值。这种方式允许函数修改外部变量的内容,适用于需要输出结果或者大型数据结构的情况。

 #include<stdio.h>#include <stdlib.h>#include <string.h>void GetMemory1(char **p) // p ==== NULL{*p = (char *)malloc(100); // 向系统申请100个字节的堆空间, 让p 指向该区域}void Test1(void){char *str = NULL;GetMemory1(&str); /// str ==== NULLstrcpy(str, "hello world"); // 提示: 拷贝字符串, 把"hello world" 拷贝到str 所指向的内存空间中printf("%s\n" , str);}int main(int argc, char const *argv[]){Test1();return 0;}

这里的传值为传地址,把char* str 的地址&str 传入GetMemonry1(char **p)作为形参,

解引用后*p 其实就是str 。调用的函数进入栈中进行内存的开辟操作,又因为str==*p,所以

即使栈销毁后str仍然指向malloc开辟的空间。

2.3  字符串使用

调用函数时,strcopy字符串时,存在'\0',

#include<stdio.h>
#include <stdlib.h>
#include <string.h>// TEST6void Test6(){char *str=(char *)malloc(100);strcpy(str, "hello");printf("TES1:%s\n" ,str); // 输出 hellostr+=6; //strcpy(str, "world");printf("TES2:%s\n" ,str); //输出 wordstr-=6;printf("TES3:%s\n" ,str); //输出 hello*(str+5)=' ';printf("TES4:%s\n" ,str); //输出 hello word}
int main(int argc, char const *argv[])
{Test6();return 0;
}

以上是本期补齐内容,若有不懂或者错误,欢迎评论与私信!!!

这篇关于传值、传址、空间释放详细图解的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/824196

相关文章

SpringBoot集成图片验证码框架easy-captcha的详细过程

《SpringBoot集成图片验证码框架easy-captcha的详细过程》本文介绍了如何将Easy-Captcha框架集成到SpringBoot项目中,实现图片验证码功能,Easy-Captcha是... 目录SpringBoot集成图片验证码框架easy-captcha一、引言二、依赖三、代码1. Ea

Java集合中的List超详细讲解

《Java集合中的List超详细讲解》本文详细介绍了Java集合框架中的List接口,包括其在集合中的位置、继承体系、常用操作和代码示例,以及不同实现类(如ArrayList、LinkedList和V... 目录一,List的继承体系二,List的常用操作及代码示例1,创建List实例2,增加元素3,访问元

SpringBoot整合easy-es的详细过程

《SpringBoot整合easy-es的详细过程》本文介绍了EasyES,一个基于Elasticsearch的ORM框架,旨在简化开发流程并提高效率,EasyES支持SpringBoot框架,并提供... 目录一、easy-es简介二、实现基于Spring Boot框架的应用程序代码1.添加相关依赖2.添

Java调用DeepSeek API的最佳实践及详细代码示例

《Java调用DeepSeekAPI的最佳实践及详细代码示例》:本文主要介绍如何使用Java调用DeepSeekAPI,包括获取API密钥、添加HTTP客户端依赖、创建HTTP请求、处理响应、... 目录1. 获取API密钥2. 添加HTTP客户端依赖3. 创建HTTP请求4. 处理响应5. 错误处理6.

Spring AI集成DeepSeek的详细步骤

《SpringAI集成DeepSeek的详细步骤》DeepSeek作为一款卓越的国产AI模型,越来越多的公司考虑在自己的应用中集成,对于Java应用来说,我们可以借助SpringAI集成DeepSe... 目录DeepSeek 介绍Spring AI 是什么?1、环境准备2、构建项目2.1、pom依赖2.2

Goland debug失效详细解决步骤(合集)

《Golanddebug失效详细解决步骤(合集)》今天用Goland开发时,打断点,以debug方式运行,发现程序并没有断住,程序跳过了断点,直接运行结束,网上搜寻了大量文章,最后得以解决,特此在这... 目录Bug:Goland debug失效详细解决步骤【合集】情况一:Go或Goland架构不对情况二:

Python itertools中accumulate函数用法及使用运用详细讲解

《Pythonitertools中accumulate函数用法及使用运用详细讲解》:本文主要介绍Python的itertools库中的accumulate函数,该函数可以计算累积和或通过指定函数... 目录1.1前言:1.2定义:1.3衍生用法:1.3Leetcode的实际运用:总结 1.1前言:本文将详

Deepseek R1模型本地化部署+API接口调用详细教程(释放AI生产力)

《DeepseekR1模型本地化部署+API接口调用详细教程(释放AI生产力)》本文介绍了本地部署DeepSeekR1模型和通过API调用将其集成到VSCode中的过程,作者详细步骤展示了如何下载和... 目录前言一、deepseek R1模型与chatGPT o1系列模型对比二、本地部署步骤1.安装oll

Java多线程父线程向子线程传值问题及解决

《Java多线程父线程向子线程传值问题及解决》文章总结了5种解决父子之间数据传递困扰的解决方案,包括ThreadLocal+TaskDecorator、UserUtils、CustomTaskDeco... 目录1 背景2 ThreadLocal+TaskDecorator3 RequestContextH

Spring Boot整合log4j2日志配置的详细教程

《SpringBoot整合log4j2日志配置的详细教程》:本文主要介绍SpringBoot项目中整合Log4j2日志框架的步骤和配置,包括常用日志框架的比较、配置参数介绍、Log4j2配置详解... 目录前言一、常用日志框架二、配置参数介绍1. 日志级别2. 输出形式3. 日志格式3.1 PatternL