Linux系列:如何用 C#调用 C方法造成内存泄露
一:背景
1. 讲故事
好久没写文章了,还是来写一点吧,今年准备多写一点 Linux平台上的东西,这篇从 C# 调用 C 这个例子开始。在 windows 平台上,我们常常在 C++ 代码中用 extern "C" 导出 C风格 的函数,然后在 C# 中用 DllImport 的方式引入,那在 Linux 上怎么玩的?毕竟这对研究 Linux 上的 C# 程序非托管内存泄露有非常大的价值,接下来我们就来看下。
二:一个简单的非托管内存泄露
1. 构建 so 文件
在 Windows 平台上我们会通过 MSVC 编译器将 C代码编译出一个成品 .dll,在 Linux 上通常会借助 gcc 将 c 编译成 .so 文件,这个.so 全称 Shared Object,为了方便讲解,先上一段简单的代码:
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#define BLOCK_SIZE (10 * 1024) // 每个块 10K
#define TOTAL_SIZE (1 * 1024 * 1024 * 1024) // 总计 1GB
#define BLOCKS (TOTAL_SIZE / BLOCK_SIZE) // 计算需要的块数
void heapmalloc()
{
uint8_t *blocks[BLOCKS]; // 存储每个块的指针
// 分配 1GB 内存,分成多个小块
for (size_t i = 0; i < BLOCKS; i++)
{
blocks[i] = (uint8_t *)malloc(BLOCK_SIZE);
if (blocks[i] == NULL)
{
printf("内存分配失败!\n");
return;
}
// 确保每个块都被实际占用
memset(blocks[i], 20, BLOCK_SIZE);
}
printf("已经分配 1GB 内存在堆上!\n");
}
接下来使用 gcc 编译,参考如下:
gcc -shared -o libmyleak.so -fPIC myleak.c
- -shared: 编译成共享库
- -fPIC: 指定共享库可以在内存任意位置被加载(地址无关性)
命令执行完之后,就可以看到一个 .so 文件了,截图如下:

最后可以用 nm 命令验证下 libmyleak.so 中是否有 Text 段下的 heapmalloc 导出函数。
root@ubuntu2404:/data2/c# nm libmyleak.so
0000000000004028 b completed.0
w __cxa_finalize@GLIBC_2.2.5
00000000000010c0 t deregister_tm_clones
0000000000001130 t __do_global_dtors_aux
0000000000003e00 d __do_global_dtors_aux_fini_array_entry
0000000000004020 d __dso_handle
0000000000003e08 d _DYNAMIC
000000000000125c t _fini
0000000000001170 t frame_dummy
0000000000003df8 d __frame_dummy_init_array_entry
00000000000020f8 r __FRAME_END__
0000000000003fe8 d _GLOBAL_OFFSET_TABLE_
w __gmon_start__
000000000000203c r __GNU_EH_FRAME_HDR
0000000000001179 T heapmalloc
0000000000001000 t _init
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
U malloc@GLIBC_2.2.5
U memset@GLIBC_2.2.5
U puts@GLIBC_2.2.5
00000000000010f0 t register_tm_clones
U __stack_chk_fail@GLIBC_2.4
0000000000004028 d __TMC_END__
2. C# 代码调用
so构建好了之后,后面就比较好说了,使用 dotnet new console -n CSharpApplication --use-program-main true 新建一个CS项目。
root@ubuntu2404:/data2/csharp# dotnet new console -n CSharpApplication --use-program-main true
The template "Console App" was created successfully.
Processing post-creation actions...
Restoring /data2/csharp/CSharpApplication/CSharpApplication.csproj:
Determining projects to restore...
Restored /data2/csharp/CSharpApplication/CSharpApplication.csproj (in 1.7 sec).
Restore succeeded.
编译下 C# 项目,然后将 libmyleak.so 放到 C#项目的 bin目录,修改 C# 代码如下:
using System.Runtime.InteropServices;
namespace CSharpApplication;
class Program
{
[DllImport("libmylib.so", CallingConvention = CallingConvention.Cdecl)]
public static extern void hello();
static void Main(string[] args)
{
hello();
Console.ReadLine();
}
}
最后用 dotnet CSharpApplication.dll 运行:
root@ubuntu2404:/data2/csharp/CSharpApplication/bin/Debug/net8.0# dotnet CSharpApplication.dll
已经分配 1GB 内存在堆上!
程序是跑起来了,那真的是吃了1G呢? 可以先用 htop 观察程序,从截图看没毛病。

那这 1G 真的在 heap 上吗? 可以用 maps 观察。
root@ubuntu2404:~# ps -ef | grep CSharp
root 10764 10730 0 13:35 pts/21 00:00:00 dotnet CSharpApplication.dll
root 11049 11027 0 13:41 pts/22 00:00:00 grep --color=auto CSharp
root@ubuntu2404:~# cat /proc/10764/maps
614e1f592000-614e1f598000 r--p 00000000 08:02 1479867 /usr/lib/dotnet/dotnet
614e1f598000-614e1f5a4000 r-xp 00005000 08:02 1479867 /usr/lib/dotnet/dotnet
614e1f5a4000-614e1f5a5000 r--p 00010000 08:02 1479867 /usr/lib/dotnet/dotnet
614e1f5a5000-614e1f5a6000 rw-p 00010000 08:02 1479867 /usr/lib/dotnet/dotnet
614e5b5d9000-614e9b8a8000 rw-p 00000000 00:00 0 [heap]
...
root@ubuntu2404:~# pmap 10764
10764: dotnet CSharpApplication.dll
0000614e1f592000 24K r---- dotnet
0000614e1f598000 48K r-x-- dotnet
0000614e1f5a4000 4K r---- dotnet
0000614e1f5a5000 4K rw--- dotnet
0000614e5b5d9000 1051452K rw--- [ anon ]
...
根据 linux 进程的内存布局,可执行image之后是 heap 堆,可以看到 [heap] 约等于1G (614e9b8a8000 - 614e5b5d9000),即 pmap 中的 1051452K。
三:总结
部署在 Linux上的.NET程序同样存在 非托管内存泄露 的问题,这篇文章的例子虽然很简单,希望能给大家带来一些思考和观测途径吧。

Linux系列:如何用 C#调用 C方法造成内存泄露的更多相关文章
- .Net中的AOP系列之《间接调用——拦截方法》
返回<.Net中的AOP>系列学习总目录 本篇目录 方法拦截 PostSharp方法拦截 Castle DynamicProxy方法拦截 现实案例--数据事务 现实案例--线程 .Net线 ...
- Java6 String.substring()方法的内存泄露
substring(start,end)在Java编程里面经常使用,没想到如果使用不当,会出现内存泄露. 要了解substring(),最好的方法便是查看源码(jdk6): /** * <blo ...
- 如何用MAT分析Android程序的内存泄露
本文结合<Android开发艺术探索>书籍中的内存分析例子来讲解如何利用MAT工具来查找内存泄漏(以AndroidStudio开发工具为例). 1.下载MAT(Eclipse Memory ...
- asp.net javascript客户端调用服务器端方法
如何用js调用服务器端方法.首先服务器端方法的格式如下 [System.Web.Services.WebMethod] public static void serverMethod(s ...
- 如何用Java编写一段代码引发内存泄露
本文来自StackOverflow问答网站的一个热门讨论:如何用Java编写一段会发生内存泄露的代码. Q:刚才我参加了面试,面试官问我如何写出会发生内存泄露的Java代码.这个问题我一点思路都没有, ...
- 图片系列(6)不同版本上 Bitmap 内存分配与回收原理对比
请点赞关注,你的支持对我意义重大. Hi,我是小彭.本文已收录到 GitHub · AndroidFamily 中.这里有 Android 进阶成长知识体系,有志同道合的朋友,关注公众号 [彭旭锐] ...
- 一步一步学Silverlight 2系列(22):在Silverlight中如何用JavaScript调用.NET代码
概述 Silverlight 2 Beta 1版本发布了,无论从Runtime还是Tools都给我们带来了很多的惊喜,如支持框架语言Visual Basic, Visual C#, IronRuby, ...
- C#编译器优化那点事 c# 如果一个对象的值为null,那么它调用扩展方法时为甚么不报错 webAPI 控制器(Controller)太多怎么办? .NET MVC项目设置包含Areas中的页面为默认启动页 (五)Net Core使用静态文件 学习ASP.NET Core Razor 编程系列八——并发处理
C#编译器优化那点事 使用C#编写程序,给最终用户的程序,是需要使用release配置的,而release配置和debug配置,有一个关键区别,就是release的编译器优化默认是启用的.优化代码 ...
- android黑科技系列——分析某直播App的协议加密原理以及调用加密方法进行协议参数构造
一.前言 随着直播技术火爆之后,各家都出了直播app,早期直播app的各种请求协议的参数信息都没有做任何加密措施,但是慢慢的有人开始利用这个后门开始弄刷粉关注工具,可以让一个新生的小花旦分分钟变成网红 ...
- 从需求的角度去理解Linux系列:总线、设备和驱动
笔者成为博客专家后整理以前原创的嵌入式Linux系列博文,现推出以让更多的读者受益. <从需求的角度去理解linux系列:总线.设备和驱动>是一篇有关如何学习嵌入式Linux系统的方法论文 ...
随机推荐
- 关于Git上传项目报错error: RPC failed; HTTP 413 curl 22 The requested URL returned error: 413
问题 今天用Git上传项目时,最后一步push时命令行报错 error: RPC failed; HTTP 413 curl 22 The requested URL returned error: ...
- Knife4j文档请求异常 app.23f8b31d.js:1 SyntaxError: Unexpected token I in JSON at position 6820
发现代码里example包含了特殊字符[] 去掉[]即可 @ApiModelProperty(value = "状态", example = "INIT, RUNNING ...
- Qt编写物联网管理平台33-设备面板
一.前言 设备面板展示数据,相对于表格展示,可能在一个页面中能够展示的设备数据量少一些,但是有些用户和场景,又需要这种面板的形式,可能更生动形象一些.尤其是经过这么些年的社会的毒打,我的原则是:用户是 ...
- [转]基于 Node.js 实现前后端分离
为了解决传统Web开发模式带来的各种问题,我们进行了许多尝试,但由于前/后端的物理鸿沟,尝试的方案都大同小异.痛定思痛,今天我们重新思考了"前后端"的定义,引入前端同学都熟悉的No ...
- C# AIModelRouter:使用不同的AI模型完成不同的任务
AIModelRouter AI模型路由,模型的能力有大小之分,有些简单任务,能力小一点的模型也能很好地完成,而有些比较难的或者希望模型做得更好的,则可以选择能力强的模型.为什么要这样做呢?可以降低A ...
- 异步 QQ 机器人框架_NoneBot
一.NoneBot使用 1) #监控发送的消息"群发"的事件@on_command('send_msg', aliases=('群发',))async def send_msg(s ...
- 2025 最佳免费商用文本转语音模型: Kokoro TTS
在文本转语音(TTS)技术领域,一项突破性的进展引起了广泛关注--Kokoro TTS 模型凭借其卓越性能和完全免费的商用许可,成为目前最出色的 TTS 解决方案之一.基于广受欢迎的开源框架 Styl ...
- Synchronized的各场景使用方法(多窗口售票例子接上篇)
同步锁机制:在<Thinking in Java>中,是这么说的:对于并发工作,你需要某种方式来防止两个任务访问相同的资源(其实就是共享资源竞争). 防止这种冲突的方法就是当资源被一个 ...
- 某次信创环境Oceanbase数据库偶发乱码问题
资料迁移,整理分享. 问题发生在2023年 一.环境介绍及问题简述 数据库 oceanbase 操作系统 Linux (麒麟) WEB中间件 Tongweb 数据库表编码 GBK 中间件默认使用的HT ...
- 边缘计算与MEC浅谈
本文分享自天翼云开发者社区<边缘计算与MEC浅谈>,作者:y****n 一.什么是边缘计算 边缘计算是在靠近物或数据源头的网络边缘侧,通过融合网络.计算.存储.应用核心能力的分布式开放平台 ...