如何释放动态链接库中申请的内存

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何释放动态链接库中申请的内存相关的知识,希望对你有一定的参考价值。

char *str = NULL;
plist_get_string_val(node, &str);
char *path = string_build_path(backup_dir, str, NULL);

free(str);

plist_get_string_val是动态链接库中导出的一个函数,
free(str)会导致应用程序失败,请问这个应该如何设置?
动态库是别人编译好的

参考技术A 第一步,我先从简单的调用出发,定义了一个简单的函数,该函数仅仅实现一个整数加法求和:

LIBEXPORT_API int mySum(int a,int b) return a+b;
C# 导入定义:

public class RefComm

[DllImport("LibEncrypt.dll",
EntryPoint=" mySum ",
CharSet=CharSet.Auto,CallingConvention=CallingConvention.StdCall)]
public static extern int mySum (int a,int b);

在C#中调用测试:

int iSum = RefComm.mySum(,);

运行查看结果iSum为5,调用正确。第一步试验完成,说明在C#中能够调用自定义的动态链接库函数。

第二步,我定义了字符串操作的函数(简单起见,还是采用前面的函数名),返回结果为字符串:

LIBEXPORT_API char *mySum(char *a,char *b)sprintf(b,"%s",a); return a;
C# 导入定义:

public class RefComm

[DllImport("LibEncrypt.dll",
EntryPoint=" mySum ",
CharSet=CharSet.Auto,
CallingConvention=CallingConvention.StdCall)]
public static extern string mySum (string a, string b);

在C#中调用测试:

string strDest="";
string strTmp= RefComm.mySum("45", strDest);

运行查看结果 strTmp 为"45",但是strDest为空。我修改动态链接库实现,返回结果为串b:

LIBEXPORT_API char *mySum(char *a,char *b)sprintf(b,"%s",a) return b;
修改 C# 导入定义,将串b修改为ref方式:

public class RefComm

[DllImport("LibEncrypt.dll",
EntryPoint=" mySum ",
CharSet=CharSet.Auto,CallingConvention=CallingConvention.StdCall)]
public static extern string mySum (string a, ref string b);

在C#中再调用测试:

string strDest="";
string strTmp= RefComm.mySum("45", ref strDest);
运行查看结果 strTmp 和 strDest 均不对,含不可见字符。再修改 C# 导入定义,将CharSet从Auto修改为Ansi:

public class RefComm

[DllImport("LibEncrypt.dll",
EntryPoint=" mySum ",
CharSet=CharSet.Ansi,CallingConvention=CallingConvention.StdCall)]
public static extern string mySum (string a, string b);

在C#中再调用测试:

string strDest="";
string strTmp= RefComm. mySum("45", ref strDest);
运行查看结果 strTmp 为"45",但是串 strDest 没有赋值。第二步实现函数返回串,但是在函数出口参数中没能进行输出。再次修改 C# 导入定义,将串b修改为引用(ref):

public class RefComm

[DllImport("LibEncrypt.dll",
EntryPoint=" mySum ",
CharSet=CharSet.Ansi,CallingConvention=CallingConvention.StdCall)]
public static extern string mySum (string a, ref string b);


运行时调用失败,不能继续执行。

第三步,修改动态链接库实现,将b修改为双重指针:

LIBEXPORT_API char *mySum(char *a,char **b)sprintf((*b),"%s",a); return *b;
C#导入定义:

public class RefComm

[DllImport("LibEncrypt.dll",
EntryPoint=" mySum ",
CharSet=CharSet.Ansi,CallingConvention=CallingConvention.StdCall)]
public static extern string mySum (string a, ref string b);

在C#中调用测试:

string strDest="";
string strTmp= RefComm. mySum("45", ref strDest);

运行查看结果 strTmp 和 strDest 均为"45",调用正确。第三步实现了函数出口参数正确输出结果。

第四步,修改动态链接库实现,实现整数参数的输出:

LIBEXPORT_API int mySum(int a,int b,int *c) *c=a+b; return *c;
C#导入的定义:

public class RefComm

[DllImport("LibEncrypt.dll",
EntryPoint=" mySum ",
CharSet=CharSet.Ansi,CallingConvention=CallingConvention.StdCall)]
public static extern int mySum (int a, int b,ref int c);

在C#中调用测试:

int c=0;
int iSum= RefComm. mySum(,, ref c);

运行查看结果iSum 和c均为5,调用正确。

经过以上几个步骤的试验,基本掌握了如何定义动态库函数以及如何在 C# 定义导入,有此基础,很快我实现了变长加密函数在 C# 中的调用,至此目标实现。

三、结论

在 C# 中调用 C++ 编写的动态链接库函数,如果需要出口参数输出,则需要使用指针,对于字符串,则需要使用双重指针,对于 C# 的导入定义,则需要使用引用(ref)定义。

对于函数返回值,C# 导入定义和 C++ 动态库函数声明定义需要保持一致,否则会出现函数调用失败。定义导入时,一定注意 CharSet 和 CallingConvention 参数,否则导致调用失败或结果异常。运行时,动态链接库放在 C# 程序的目录下即可,我这里是一个 C# 的动态链接库,两个动态链接库就在同一个目录下运行。

动态内存管理详解

申请内存的方式

最简单的申请内存方式:创建变量

(1)局部变量:出了当前代码块就释放内存
(2)全局变量:程序运行结束就释放内存
(3)静态变量:程序运行结束就释放内存

动态内存管理 (即内存的申请和释放 )

目的:

程序运行过程中,更灵活的进行内存的申请和释放~
即:随时需要就申请,随时不要就释放

(一般这样理解)
动态往往是和“运行时”相关~
静态往往是和“编译器”相关~

申请 / 释放操作

由C语言提供的几个库函数完成

1.申请内存:malloc函数

函数原型:
void* malloc (size_t size);

malloc注意事项:

  1. size表示申请多少个字节的内存
  2. malloc申请到的是一块连续的内存空间
  3. 申请到的内存空间并没有指定类型
  4. 返回值为void*类型,只关注内存大小,不关注大小(大小隐含在参数size里)
  5. 如果申请成功,则返回申请到内存的起始位置的地址
  6. 如果申请失败,就会返回一个NULL,在笔试/面试中使用malloc时一定要检查
  7. malloc申请到的内存,内存会一直存在,直到手动释放(free)或者程序结束为止

代码示例:

int main() {
	//malloc申请到的这块空间,可以当成一个“数组”去使用
	//其用法类似于数组
	int* p = (int*) malloc(4 * sizeof(int));
	for (int i = 0; i < 4; i++) {
		p[i] = i;
	}
	for (int i = 0; i < 4; i++) {
		printf("%d\\n", p[i]);
	}
	system("pause");
	return 0;
}

2.释放内存:free函数

函数原型:
void free (void* ptr);

功能:释放一个通过动态内存方式申请到的内存空间
free注意事项:

  1. 若参数 ptr 指向的空间不是动态申请的,会发生未定义行为
  2. 参数必须传入malloc等动态申请内存函数返回的地址,返回的是什么,就传入什么
  3. 不能重复释放同一块内存空间

错误代码示例

//错误代码1
int num = 10;
free(&num);

//错误代码2
int* p = (int*)malloc(4 * sizeof(int));
free(p + 1);
	
//错误代码3
int* p = (int*)malloc(4 * sizeof(int));
free(p);
free(p);

//错误代码4
int* p = (int*)malloc(4 * sizeof(int));
p = (int*)malloc(4 * sizeof(int));
free(p);

在这里插入图片描述等进程结束来释放内存,大概率会发生内存泄漏(memory leak)

内存泄漏的危害:
如果一直持续不断的申请内存而不主动释放,系统中剩余的内存就会越来越少,最终会导致没有内存可以使用,此时程序也会崩溃,若是服务器程序,会更严重
在C语言中,没有太好的方法避免内存泄漏,最稳妥的方法就是不用malloc
C++中,可以使用智能指针,一定程度可以缓解内存泄漏问题
Java / Python / Go / JS等中,通过垃圾回收机制(GC),来解决内存泄露问题
垃圾回收机制,相对于智能指针,能够很大程度的解决内存泄漏问题

3.calloc函数

函数原型:
void* calloc (size_t num, size_t size);

calloc注意事项:

  1. calloc申请到的内存空间,会自动初始化为全0
  2. 为num个元素的数组分配一块内存,每个元素是size字节长,并将其所有位初始化为零。

实际开发中并不常用calloc 简单介绍一下即可

4.realloc函数

函数原型:
void* realloc (void* ptr, size_t size);、

功能:针对当前malloc / calloc申请到的内存空间进行扩容

realloc注意事项:

  1. realloc后的内存空间仍然是连续的内存空间
  2. ptr是要调整的内存地址
  3. size是调整之后的内存大小
  4. realloc得到的地址,最终不用时,也需要通过free来释放
  5. 故:void * ptr2 = realloc(ptr,size),此处的ptr和ptr2可能相等,也可能不相等

realloc进行扩容的两种情况:
(从10扩容到20为例:)

情况一:原有空间足够大
在这里插入图片描述
情况二:原有空间大小不够
在这里插入图片描述
realloc两种情况总结:

  1. 原有的空间有足够大,够容纳下size个字节的内存,直接在原有内存后继续追加即可,原来的空间数据不发生改变
  2. 原有的空间不够大,要扩容的话,必须在堆上另外找一个连续的内存空间,可以存放size个字节大小,把原来内存的数据拷贝过去,并且释放旧空间

动态内存管理缺点

动态内存管理太过灵活,故太容易出错
但C语言中没有其他方案,故还是需广泛使用动态内存管理~
后面会发一些常见的错误代码~~

以上是关于如何释放动态链接库中申请的内存的主要内容,如果未能解决你的问题,请参考以下文章

如何使用动态链接库中的资源

进程间通信 - 动态链接库中共享内存(利用DLL的2~3G的地址段空间)

链接器如何在剥离的动态库中定位代码?

如何识别IDA反汇编中动态链接库中的函数

DLL隐式链接

Linux动态链接之GOT与PLT