尽可能少的字节泄漏内存


79

您的任务是编写代码,该代码将在尽可能少的字节中泄漏至少一个字节的内存。内存必须泄漏,而不仅仅是分配

内存泄漏是程序分配的内存,但是在可以正确地重新分配内存之前,它无法访问。对于大多数高级语言,此内存必须在堆上分配。

以下程序是C ++中的一个示例:

int main(){new int;}

这使new int堆上没有指针。由于我们无法访问该内存,因此该内存立即被泄漏。

以下是Valgrind的泄漏摘要:

LEAK SUMMARY:
   definitely lost: 4 bytes in 1 blocks
   indirectly lost: 0 bytes in 0 blocks
     possibly lost: 0 bytes in 0 blocks
   still reachable: 0 bytes in 0 blocks
        suppressed: 0 bytes in 0 blocks

如果可以的话,许多语言都具有内存调试器(例如Valgrind),您应该包括此类调试器的输出以确认内存泄漏。

目标是最大程度地减少源中的字节数。


2
也许您可能会有不同范围的泄漏量,并且根据泄漏量的多少,您损失的字节数是x%
Christopher

11
@ChristopherPeart对于一个挑战者,我不喜欢奖金,对于两个挑战者,正如您已经表明的那样,泄漏无限内存非常容易。
精神分裂症O'Zaic,2017年

1
相关的。但是,这不是重复的,因为对该问题的大多数答案在内存中形成了无限的可访问结构,而不是实际泄漏内存。

2
有什么想法?记忆不能释放吗?我猜想这将要求本机执行垃圾收集的语言或利用bug。
akostadinov

7
我看到为高尔夫设计的语言在这门语言上如何惨败...
Kh40tiK

Answers:


89

Perl(5.22.2),0个字节

在线尝试!

我知道那里有某种语言会在一个空程序中泄漏内存。我原以为这是一个esolang,但事实证明,这会perl泄漏任何程序的内存。(我假设这是故意的,因为如果您知道无论如何都要退出内存,则释放内存只会浪费时间;因此,如今的常见建议是,一旦进入程序的退出例程,就泄漏所有剩余的内存。 )

验证

$ echo -n | valgrind perl
snip
==18517== 
==18517== LEAK SUMMARY:
==18517==    definitely lost: 8,134 bytes in 15 blocks
==18517==    indirectly lost: 154,523 bytes in 713 blocks
==18517==      possibly lost: 0 bytes in 0 blocks
==18517==    still reachable: 0 bytes in 0 blocks
==18517==         suppressed: 0 bytes in 0 blocks
==18517== 
==18517== For counts of detected and suppressed errors, rerun with: -v
==18517== ERROR SUMMARY: 15 errors from 15 contexts (suppressed: 0 from 0)

16
我喜欢Unlambda的答案,但是这很麻烦(IMHO),因为很明显是解释器本身泄漏了内存,也就是说,当我perl --version在计算机上运行时,我肯定会“丢失:14个块中的7,742个字节” ,尽管它根本无法运行任何程序。
齐柏林飞艇

11
@zeppelin:同意,但是根据我们的规则,定义语言的是实现,因此,如果实现泄漏内存,则该语言中的所有程序都会泄漏内存。我不一定要确定我是否同意该规则,但是在这一点上,它根深蒂固,无法真正改变。

8
这也可以在Node JS中使用。
丹尼斯,

6
这感觉就像是制作过程中的新标准漏洞……
Michael Hampton

46
最后是我能理解的Perl脚本。
user11153 '17

66

C,48 31 22字节

警告:不要运行太多次。

感谢Dennis的大量帮助/想法!

f(k){shmget(k,1,512);}

这更进一步。shmget分配程序结束时未释放的共享内存。它使用键来标识内存,因此我们使用未初始化的int。从技术上讲,这是未定义的行为,但是实际上,这意味着调用此函数时,我们使用的值恰好位于堆栈顶部的上方。下次将任何内容添加到堆栈中时,将写入此内容,因此我们将丢失密钥。


唯一不起作用的情况是,您可以弄清楚之前的堆栈。对于额外的19个字节,您可以避免此问题:

f(){srand(time(0));shmget(rand(),1,512);}

或者,对于26个字节:

main(k){shmget(&k,1,512);}

但是有了这个,程序退出后内存就会泄漏。在运行时,程序可以访问违反规则的内存,但是在程序终止后,我们将无法访问密钥,并且仍会分配内存。这需要地址空间布局随机化(ASLR),否则&k将始终相同。如今,ASLR通常默认情况下处于启用状态。


验证:

您可以ipcs -m用来查看系统上存在哪些共享内存。为了清楚起见,我删除了先前存在的条目:

$ cat leakMem.c 
f(k){shmget(k,1,512);}
int main(){f();}     
$ gcc leakMem.c -o leakMem
leakMem.c:1:1: warning: return type defaults to ‘int’ [-Wimplicit-int]
 f(k){shmget(k,1,512);}
 ^
leakMem.c: In function ‘f’:
leakMem.c:1:1: warning: type of ‘k’ defaults to ‘int’ [-Wimplicit-int]
leakMem.c:1:6: warning: implicit declaration of function ‘shmget’ [-Wimplicit-function-declaration]
 f(k){shmget(k,1,512);}
ppcg:ipcs -m

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      


$ ./leakMem 

$ ipcs -m

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      

0x0000007b 3375157    Riley      0          1          0  

1
从理论上讲,shmid可能已经存储在文件中,并且将来程序可能会附加到该文件。这就是unix共享内存的工作原理……
tbodt

1
@AndrewSavinykh共享内存基本上成为OS可以提供​​给其他进程的资源。它类似于驻留在RAM中的文件,并且知道名称(键)的任何进程都可以对其进行访问,直到将其删除。想象一个计算数字并将其存储在内存中的进程,然后在读取数据的进程连接到共享内存之前退出。在这种情况下,如果操作系统释放了内存,那么第二个进程将无法获取它。
莱利

35
感谢您发布此信息。我只是保护TIO免受共享内存泄漏。
丹尼斯

4
@Dennis这就是为什么我没有发布TIO链接的原因。我不知道它是否受到保护。
莱利

12
我喜欢您使用问题一词来描述程序泄漏的内存少于预期的情况。
kasperd

40

Unlambdac-refcnt/unlambda),1个字节

i

在线尝试!

这对于找到一个预先存在的解释器是非常困难的,该解释器会在非常简单的程序上泄漏内存。在这种情况下,我使用了Unlambda。官方的Unlambda解释器不止一个,但它c-refcnt是最易于构建的解释器之一,它具有有用的属性,即在程序成功运行时会泄漏内存。因此,我在这里只需要提供最简单的合法Unlambda程序即可,无操作。(请注意,空程序在这里不起作用;在解释器崩溃时,仍然可以访问内存。)

验证

$ wget ftp://ftp.madore.org/pub/madore/unlambda/unlambda-2.0.0.tar.gz
…剪断…
2017-02-18 18:11:08(975 KB / s)-保存了'unlambda-2.0.0.tar.gz'[492894]
$ tar xf unlambda-2.0.0.tar.gz 
$ cd unlambda-2.0.0 / c-refcnt /
$ gcc unlambda.c
$ echo -ni | valgrind ./a.out / dev / stdin
…剪断…
== 3417 ==泄漏摘要:
== 3417 ==绝对丢失:1块中40字节
== 3417 ==间接丢失:0个字节,共0个块
== 3417 ==可能丢失:0字节,共0个块
== 3417 ==仍可到达:0字节,位于0块中
== 3417 ==已抑制:0个字节,共0个块
== 3417 == 
== 3417 ==有关检测到的和抑制的错误的计数,请重新运行:-v
== 3417 ==错误摘要:来自1个上下文的1个错误(禁止显示:0至0)

39

TI基本(12字节)

While 1
Goto A
End
Lbl A
Pause 

“ ...内存泄漏是您在循环内使用Goto / Lbl或在到达End命令之前,如果有条件(任何具有End命令的条件)跳出该控制结构的地方...” (更多)


7
哇,我想我还记得这一点。我一直跳出旧的基本程序的循环,并注意到我的TI-84 +越来越慢了……
Ray

2
是的,我们大多数人都知道这种感觉;)@RayKoopa
Timtech '17

13
钛基本款+1。我九年级的大部分时间都在为这些东西编程。
markasoftware

Pause 最后需要吗?您可以节省2个字节。
kamoroso94 '17

@ kamoroso94我认为是这样,因为“如果程序结束,则泄漏将被清除,并且不会导致进一步的问题”,因此它是要阻止程序结束。
Timtech '17


27

C#,34个字节

class L{~L(){for(;;)new L();}}

解决方案不需要堆。它只需要一个真正努力工作的GC(垃圾收集器)。

本质上,它将GC变成自己的敌人。

说明

每当调用析构函数时,只要超时用完,它都会创建此邪恶类的新实例,并告诉GC仅丢弃该对象,而无需等待析构函数完成。到那时,已经创建了数千个新实例。

这种“邪恶”是,GC越努力工作,它吹到您脸上的次数就越多。

免责声明:您的GC可能比我的智能。程序中的其他情况可能导致GC忽略第一个对象或其析构函数。在这些情况下,它不会爆炸。但在许多变体中。在这里和那里添加一些字节可能会确保在每种可能的情况下泄漏。好吧,除了电源开关。

测试

这是一个测试套件

using System;
using System.Threading;
using System.Diagnostics;
class LeakTest {
    public static void Main() {
        SpawnLeakage();
        Console.WriteLine("{0}-: Objects may be freed now", DateTime.Now);
        // any managed object created in SpawbLeakage 
        //  is no longer accessible
        // The GC should take care of them

        // Now let's see
        MonitorGC();
    }
    public static void SpawnLeakage() {
        Console.WriteLine("{0}-: Creating 'leakage' object", DateTime.Now);
        L l = new L();
    }
    public static void MonitorGC() {
        while(true) {
            int top = Console.CursorTop;
            int left = Console.CursorLeft;
            Console.WriteLine(
                "{0}-: Total managed memory: {1} bytes",
                DateTime.Now,
                GC.GetTotalMemory(false)
            );
            Console.SetCursorPosition(left, top);
        }
    }
}

10分钟后输出:

2/19/2017 2:12:18 PM-: Creating 'leakage' object
2/19/2017 2:12:18 PM-: Objects may be freed now
2/19/2017 2:22:36 PM-: Total managed memory: 2684476624 bytes

那就是2684476624字节。总WorkingSet的过程中为约4.8 GB

这个答案的灵感来自埃里克·利珀特(Eric Lippert)的精彩文章:当您所知的一切都不对时


这很有趣。垃圾收集器是否会“忘记”某些事物的存在并因此而失去对它们的跟踪?我对C#不太了解。现在我也想知道,炸弹和泄漏之间有什么区别?我想类似的惨败,可以通过创建从构造函数内部调用构造函数,或具有永不停止无限递归函数,虽然在技术上的系统从来没有失去跟踪这些引用,它只是用完的空间...
唐明亮

1
构造函数中的构造函数将导致堆栈溢出。但是实例的析构函数在平面层次结构中被调用。实际上,GC永远不会丢失对象。只要它试图销毁它们,就会无意间创建新对象。另一方面,用户代码无权访问所述对象。此外,由于GC可能决定销毁对象而不调用其析构函数,因此可能会出现上述不一致之处。
MrPaulch '17

仅仅使用挑战就不会完成挑战class L{~L(){new L();}}吗?AFAIK for(;;)唯一可以使其更快地泄漏内存,对吗?
BgrWorker

1
可悲的是没有。由于对于每个销毁的对象,仅将创建一个新实例,因此又无法访问该实例并将其标记为销毁。重复。只有一个物体即将被销毁。没有增加的人口。
MrPaulch '17

2
并不是的。最终一个定稿将被忽略。无论如何,将吃掉相应的对象。
MrPaulch '17

26

C(gcc),15个字节

f(){malloc(1);}

验证

$ cat leak.c
f(){malloc(1);}
main(){f();}
$ gcc -g -o leak leak.c
leak.c: In function ‘f’:
leak.c:1:5: warning: incompatible implicit declaration of built-in function ‘malloc’ [enabled by default]
 f(){malloc(1);}
     ^
$ valgrind --leak-check=full ./leak
==32091== Memcheck, a memory error detector
==32091== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==32091== Using Valgrind-3.10.0 and LibVEX; rerun with -h for copyright info
==32091== Command: ./leak
==32091==
==32091==
==32091== HEAP SUMMARY:
==32091==     in use at exit: 1 bytes in 1 blocks
==32091==   total heap usage: 1 allocs, 0 frees, 1 bytes allocated
==32091==
==32091== 1 bytes in 1 blocks are definitely lost in loss record 1 of 1
==32091==    at 0x4C29110: malloc (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so)
==32091==    by 0x40056A: f (leak.c:1)
==32091==    by 0x40057A: main (leak.c:2)
==32091==
==32091== LEAK SUMMARY:
==32091==    definitely lost: 1 bytes in 1 blocks
==32091==    indirectly lost: 0 bytes in 0 blocks
==32091==      possibly lost: 0 bytes in 0 blocks
==32091==    still reachable: 0 bytes in 0 blocks
==32091==         suppressed: 0 bytes in 0 blocks
==32091==
==32091== For counts of detected and suppressed errors, rerun with: -v
==32091== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

26

Javascript,14个字节

打高尔夫球

setInterval(0)

使用默认延迟注册一个空的间隔处理程序,并丢弃所得的计时器ID(无法取消)。

enter image description here

我使用了一个非默认间隔来创建数百万个计时器,以说明泄漏,因为使用默认间隔会像疯了一样吃掉CPU。


5
哈哈,我喜欢您输入的是“ Golfed”,让我对非高尔夫版本感到好奇
Martijn

9
它可能看起来像这样if(window && window.setInterval && typeof window.setInterval === 'function') { window.setInterval(0); }
Tschallacka

3
实际上,这并不是不可能取消的:间隔(和超时)ID是按顺序编号的,因此仅通过clearInterval递增ID 调用直到间隔消失就可以取消它。例如:for(let i=0;i<1e5;i++){try{clearInterval(i);}catch(ex){}}
user2428118 '17

5
@ user2428118正如齐柏林飞艇所说的那样,这比说C / C ++泄漏不是“真实的”更“合法”,因为您可以强行致电free()
TripeHound

1
哇,JavaScript真正成为竞争者的挑战并不多...
Jared Smith,

19

Java,10个字节

最后,Java有竞争力的答案!

打高尔夫球

". "::trim

这是一个方法参考(针对字符串常量),可以像这样使用:

Supplier<String> r = ". "::trim

文字字符串". "将自动添加到该类维护的全局内部字符串池中,java.lang.String并且在我们立即对其进行修整时,无法在代码中进一步重复使用对它的引用(除非再次声明完全相同的字符串)。

...

最初为空的字符串池由String类私有维护。

所有文字字符串和字符串值常量表达式均已插入。字符串文字是在Java™语言规范的3.10.5节中定义的。

...

https://docs.oracle.com/javase/8/docs/api/java/lang/String.html#intern--

您可以通过将字符串添加到自身中,然后在循环中显式调用intern()方法来解决“生产级”内存泄漏的问题。


2
我考虑过使用C#...,但我认为这不重要,因为正如您所说,您可以通过包含另一个字符串文字访问该内存。我也想知道("." + " ").intern()会做什么(如果它们是用户输入或w / e,所以我们不赞成编译器优化)。
VisualMelon '17

1
的确,唯一的共识是充其量是渺茫的,我坚定地站在“代码应该编译”的一边。考虑到问题的措辞,我仍然不确定是否购买此解决方案(这些字符串无法释放,即使在不太可能的情况下可以在常规操作代码中找到它们),但我请其他人做出自己的判断
VisualMelon

3
该字符串甚至都无法访问,更不用说泄漏了。我们可以随时通过插入相等的字符串来检索它。如果这算在内,则任何未使用的全局变量(Java中的私有静态)将泄漏。那不是定义内存泄漏的方式。
user2357112 '17

3
@ user2357112“ ...该字符串甚至都无法访问...”这看起来很明显,因为您看到了代码。现在考虑您获得了该方法引用X()作为代码的参数,您知道它在内部分配(和实习)了字符串文字,但是您不知道确切地是哪个,它可能是“。”或“ 123”或(通常)未知长度的任何其他字符串。您能否演示如何仍然可以访问它,或在它占用的“ intern”池中重新分配条目?
齐柏林飞艇

2
@ user2357112在具有有限内存的计算机上,您可以访问存储在任何内存中的值simply by guessing it correctly,但这并不意味着不存在内存泄漏之类的事情。there's probably some way to use reflection to determine the string's contents too你能证明这个吗?(提示,String.intern()在本代码中实现)。
齐柏林飞艇

17

Rust,52个字节

extern{fn malloc(_:u8);}fn main(){unsafe{malloc(9)}}

使用系统malloc分配一些字节。假设错误的ABI是可以接受的。


Rust(理论上),38个字节

fn main(){Box::into_raw(Box::new(1));}

我们在堆上分配内存,提取原始指针,然后忽略它,有效地泄漏了它。(Box::into_raw短于std::mem::forget)。

但是,Rust默认情况下使用jemalloc,而valgrind无法检测到任何泄漏。我们可以切换到系统分配器,但这会增加50个字节,并且需要每晚进行一次。非常感谢您的内存安全。


第一个程序的输出:

==10228== Memcheck, a memory error detector
==10228== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==10228== Using Valgrind-3.12.0.SVN and LibVEX; rerun with -h for copyright info
==10228== Command: ./1
==10228== 
==10228== 
==10228== HEAP SUMMARY:
==10228==     in use at exit: 9 bytes in 1 blocks
==10228==   total heap usage: 7 allocs, 6 frees, 2,009 bytes allocated
==10228== 
==10228== LEAK SUMMARY:
==10228==    definitely lost: 9 bytes in 1 blocks
==10228==    indirectly lost: 0 bytes in 0 blocks
==10228==      possibly lost: 0 bytes in 0 blocks
==10228==    still reachable: 0 bytes in 0 blocks
==10228==         suppressed: 0 bytes in 0 blocks
==10228== Rerun with --leak-check=full to see details of leaked memory
==10228== 
==10228== For counts of detected and suppressed errors, rerun with: -v
==10228== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

太棒了 这样的帖子使我在过去的一年中探索了Rust,这绝对是我尝试学习的最有趣的语言之一。
不要明亮的

16

8086 ASM,3个字节

本示例假定链接了C运行时。

jmp _malloc

这组装到e9 XX XX哪里XX XX是相对地址_malloc

这将调用malloc以分配不可预测的内存量,然后立即返回,从而终止进程。在某些操作系统(例如DOS)上,直到系统重新启动后,内存才可能根本无法回收!


malloc的常规实现将导致在进程退出时释放内存。
约书亚

@Joshua是的,但这就是实现定义的行为。
FUZxxl

12

第四个字节

打高尔夫球

s" " *

用分配一个空字符串s" ",将其地址和长度(0)留在堆栈上,然后将它们相乘(导致内存地址丢失)。

瓦尔格朗德

%valgrind --leak-check=full gforth -e 's" " * bye'
...
==12788== HEAP SUMMARY:
==12788==     in use at exit: 223,855 bytes in 3,129 blocks
==12788==   total heap usage: 7,289 allocs, 4,160 frees, 552,500 bytes allocated
==12788== 
==12788== 1 bytes in 1 blocks are definitely lost in loss record 1 of 22
==12788==    at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==12788==    by 0x406E39: gforth_engine (in /usr/bin/gforth-0.7.0)
==12788==    by 0x41156A: gforth_go (in /usr/bin/gforth-0.7.0)
==12788==    by 0x403F9A: main (in /usr/bin/gforth-0.7.0)
==12788== 
...
==12818== LEAK SUMMARY:
==12818==    definitely lost: 1 bytes in 1 blocks
==12818==    indirectly lost: 0 bytes in 0 blocks

10

去45个字节

package main
func main(){go func(){for{}}()}

这将创建一个带有无限循环的匿名goroutine。程序可以继续正常运行,因为启动goroutine就像生成并发运行的小线程一样,但是该程序无法收回为goroutine分配的内存。垃圾收集器永远不会收集它,因为它仍在运行。有人称这为“泄露goroutine”


高尔夫检查:这比通话短2字节C.malloc(8),因为您需要import"C"
瑞金(Riking)

9

Java 1.3,23个字节

void l(){new Thread();}

创建一个线程但不启动它。该线程已在内部线程池中注册,但是永远不会启动,因此永远不会结束,因此永远不会成为GC的候选对象。它是无法检索的对象,被卡在Java领域中。

这是Java中的错误,直到包含1.3为止,此后已修复。

测试中

以下程序确保使用新的线程对象污染内存,并显示减少的可用内存空间。为了进行泄漏测试,我专门使GC运行。

public class Pcg110485 {

    static
    void l(){new Thread();}

    public static void main(String[] args) {

        while(true){
            l();
            System.gc();
            System.out.println(Runtime.getRuntime().freeMemory());
        }
    }
}

由于这仅适用于特定的Java版本,因此您应该在标题中说“ Java 3”。

5
没有像Java 3这样的东西。它是Java 1.3。有Java 1.0、1.1、2、1.3、1.4、5、6、7、8、9。怪异的编号,但事实就是如此。
OlivierGrégoire'17

这也是我的主意。该死的。
Magic Octopus Urn

8

Befunge(真菌),1个字节

$

这可能取决于平台和版本(我仅在Windows上使用1.0.4版本进行了测试),但是真菌一直以来都是非常泄漏的解释器。该$(降)命令不应该做一个空栈上的任何东西,但遍历该代码某种程度上设法很快泄漏大量的内存。在短短的几秒钟内,它将用完几场演出,并因“内存不足”错误而崩溃。

请注意,它不一定必须是$命令-几乎可以做任何事情。但是,它不能与空白的源文件一起使用。至少必须进行一次手术。


8

Swift 3,38个字节

新版本:

class X{var x: X!};do{let x=X();x.x=x}

x 对自身有很强的引用,因此不会被释放,从而导致内存泄漏。

旧版本:

class X{var y:Y!}
class Y{var x:X!}
do{let x=X();let y=Y();x.y=y;y.x=x}

x包含对的强烈引用y,反之亦然。因此,两者都不会被释放,从而导致内存泄漏。


嗯,您仍然可以通过x和引用该内存y,因此这对我来说似乎并不是泄漏(除非您以某种方式销毁它们)。
齐柏林飞艇

@zeppelin最后一行可以包装成一个函数来解决这一问题
NobodyNada

@NobodyNada,如果我将最后一行放在do可以解决齐柏林飞艇提出的问题的代码中,对吗?
丹尼尔

@Dopapp是的;一个do也可以。好主意!
没有人

它可以缩短,您不需要两个类-X可以保存对自身的引用: class X{var x: X!};do{let x=X();x.x=x}
SebastianOsiński17年

7

Delphi(对象Pascal)-33字节

创建一个没有变量的完整控制台程序的对象:

program;begin TObject.Create;end.

在项目中启用FastMM4将显示内存泄漏:

enter image description here


6

C#-84字节

class P{static void Main(){System.Runtime.InteropServices.Marshal.AllocHGlobal(1);}}

这将恰好分配1个字节的非托管内存,然后丢失IntPtr,我相信这是获取或释放它的唯一方法。您可以通过将其填充到循环中并等待应用程序崩溃进行测试(可能希望添加一些零以加快速度)。

我考虑过了System.IO.File.Create("a");,但我不相信这些一定是内存泄漏,因为应用程序本身收集内存,可能是底层操作系统泄漏(因为CloseDispose未被调用)。文件访问的内容也需要文件系统权限,并且没有人希望依赖这些权限。事实证明,这无论如何都不会泄漏,因为没有什么可以阻止终结器的调用(它可能释放底层资源),该框架包括(在某种程度上)减轻了这类判断错误,并且使看似不确定的文件锁定(如果您是愤世嫉俗的人)使程序员感到困惑。感谢乔恩·汉纳(Jon Hanna)直截了当地说我。

我对找不到更短的方法感到有点失望。.NET GC可以正常工作,我想不出IDisposablesmscorlib中肯定会泄漏的任何东西(确实它们似乎都有终结器,这很烦人),我不知道有任何其他方式来分配非托管内存(缺少PInvoke ),以及反射确保任何与它的一个引用(不管语言的语义(例如私有成员或类,没有访问器))被发现。


1
System.IO.File.Create("a")不会泄漏任何内容,但是GC.SuppressFinalize(System.IO.File.Create("a"))会明确要求不要运行FileStream产生的终结器。
乔恩·汉娜

@JonHanna完全正确。我的IDisposable妄想症似乎对我有好处。
VisualMelon

您可能有机会使用System.Drawing.Bitmap使GDI +泄漏。不知道它是否有价值,因为它是导致泄漏的Windows库,而不是程序本身。
BgrWorker

@BgrWorker他们无疑也有一个终结器,我倾向于避免在代码高尔夫中使用外部库,因为我不同意将它们花费在成本上的共识:如果您可以找到自己有信心的方法,请随时发布它在你自己的答案!
VisualMelon

<!-- language: lang-c# -->谢谢您的回答!(它是C#,所以我很喜欢)
Metoniem

5

因子,13字节

Factor具有自动内存管理功能,但也可以访问某些libc功能:

1 malloc drop

手动分配1个字节的内存,返回它的地址,然后丢弃它。

malloc 实际注册一个副本以跟踪内存泄漏和两次释放,但是识别泄漏的对象并非易事。

如果您希望确保确实丢失了该参考:

1 (malloc) drop

测试泄漏与[ 1 malloc drop ] leaks.说:

| Disposable class | Instances |                    |
| malloc-ptr       | 1         | [ List instances ] |

测试泄漏与[ 1 (malloc) drop ] leaks.说:

| Disposable class | Instances | |

不好了!可怜的因素,现在得了老年痴呆症!D:


5

Common Lisp(仅适用于SBCL), 28 26字节

sb-alien::(make-alien int)

您可以这样运行:sbcl --eval 'sb-alien::(make-alien int)'; 没有打印或返回任何内容,但是发生了内存分配。如果我将表单包装在内(print ...),则指针将显示在REPL中。

  1. package::(form)是SBCL中的一种特殊表示法,用于在读取表单时临时绑定当前程序包。此处用于避免make-alien和都int加上前缀sb-alien。我认为将当前程序包设置为该程序包是一种欺骗,因为启动时并非如此。

  2. make-alien 为给定类型和可选大小分配内存(使用malloc)。

  3. 在REPL中执行此操作时,请0在分配之后添加,以便REPL不返回指针,而是返回该值。否则,就没有真正的泄漏,因为REPL记住最后三个返回的值(参见******),我们仍然可以有机会释放分配的内存。

感谢PrzemysławP删除了2个字节,谢谢!


1
您不能使用1(或23等)代替,()以便您返回值1吗?它将节省1个字节。也是这个答案只是REPL吗?也许如果您在其中加载代码,load您将不能()在末尾添加或包含任何内容,因为无论如何将无法访问它?
PrzemysławP

1
@PrzemysławP您在这两点上都是对的,我尝试过,eval并且按您所说的那样有效。非常感谢!
coredump

4

AutoIt,39个字节

#include<Memory.au3>
_MemGlobalAlloc(1)

从堆中分配一个字节。由于返回的句柄_MemGlobalAlloc已被丢弃,因此无法显式释放该分配。


3

C ++,16个字节

main(){new int;}

我没有valgrind检查它是否泄漏,但是可以肯定。否则我会尝试:

main(){[]{new int;}();}

Valgrind结果

(确实泄漏)

==708== LEAK SUMMARY:
==708==    definitely lost: 4 bytes in 1 blocks

我正在使用@WheatWizard g++ 4.3.2(不是最新版本),并且可以正常编译。int我认为默认情况下没有返回类型。随着-Wall我有一个警告,虽然:plop.cpp:1: warning: ISO C++ forbids declaration of 'main' with no type
matovitch

2
@WheatWizard抱歉,我刚刚看到您举了c ++示例开始比赛。来自reddit,我只看了答案,(奇怪的是)没有看到任何C ++。我有点傻。:/
matovitch

共识是您可以将其[]{new int;}视为C ++函数(挑战并未指定整个程序)。
Toby Speight,

3

Java(OpenJDK 9)322220字节

import sun.misc.*;class Main{static void main(String[]a)throws Exception{java.lang.reflect.Field f=Unsafe.class.getDeclaredField("theUnsafe");f.setAccessible‌​(1<2);((Unsafe)f.get‌​(1)).allocateMemory(‌​1);}}

在线尝试!

这是另一个不使用字符串缓存的内存泄漏。它分配了您的RAM的一半,您无法执行任何操作。

感谢Zeppelin保存所有字节


您可以通过Unsafe从其中的静态变量获取实例来节省大量字节,例如:import sun.misc.*;class M{static void main(String[]a)throws Exception{java.lang.reflect.Field f=Unsafe.class.getDeclaredField("theUnsafe");f.setAccessible(1<2);((Unsafe)f.get(1)).allocateMemory(1);}}
zeppelin

而且,您还可以通过用public static void main静态初始化程序代替来节省更多(启动时static{try{}catch(Exception e){}}可能会比较棘手,但仍然有效且可编译)。
齐柏林飞艇

是的,在我使用的Android版本的代码中使用了构造函数。我将在im @home时更改某些内容,但是我将使用一条语句来完成您的工作;)
Serverfrog

删除空格,使用a代替args,然后删除public。tio.run/nexus/…–
Pavel

true可以替换为1> 0
masterX244 '02

3

c,9个字节

main(){}

证明:

localhost/home/elronnd-10061: cat t.c
main(){}
localhost/home/elronnd-10062: valgrind gcc t.c
==10092== Memcheck, a memory error detector
==10092== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==10092== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==10092== Command: gcc t.c
==10092==
t.c:1:1: warning: return type defaults to ‘int’ [-Wimplicit-int]
 main(){}
 ^~~~
==10092==
==10092== HEAP SUMMARY:
==10092==     in use at exit: 178,518 bytes in 73 blocks
==10092==   total heap usage: 362 allocs, 289 frees, 230,415 bytes allocated
==10092==
==10092== LEAK SUMMARY:
==10092==    definitely lost: 4,659 bytes in 8 blocks
==10092==    indirectly lost: 82 bytes in 5 blocks
==10092==      possibly lost: 0 bytes in 0 blocks
==10092==    still reachable: 173,777 bytes in 60 blocks
==10092==         suppressed: 0 bytes in 0 blocks
==10092== Rerun with --leak-check=full to see details of leaked memory
==10092==
==10092== For counts of detected and suppressed errors, rerun with: -v
==10092== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

1
您实际上并没有在泄漏内存;gcc是。这也应与空程序一起使用。试试看gcc src.c && valgrind ./a.out,这应该会产生干净的结果。

3

C#,109个字节

public class P{static void Main({for(;;)System.Xml.Serialization.XmlSerializer.FromTypes(new[]{typeof(P)});}}

我们在生产代码中发现了这种泄漏背后的想法,并对其进行了研究,从而得出了本文。主要问题是该文章的长篇幅文章(有关更多信息,请阅读该文章):

在我的代码中搜索PurchaseOrder,我在page_load其中一页中找到了这行代码XmlSerializer serializer = new XmlSerializer(typeof(PurchaseOrder), new XmlRootAttribute(“”));

这似乎是一段很纯真的代码。我们创建XMLSerializerPurchaseOrder。但是幕后情况如何?

如果我们看一下XmlSerializer带有Reflector 的构造函数,就会发现它调用了this.tempAssembly = XmlSerializer.GenerateTempAssembly(this.mapping, type, defaultNamespace, location, evidence);它会生成一个临时(动态)程序集。因此,每次运行此代码(即每次单击页面)时,它将生成一个新的程序集。

生成程序集的原因是它需要生成用于序列化和反序列化的函数,并且这些函数必须驻留在某个地方。

好的,很好……它创建了一个装配,那又如何呢?完成后,它应该消失了吧?

好吧……程序集不是GC堆上的对象,GC确实不知道程序集,因此不会收集垃圾。摆脱1.0和1.1中的程序集的唯一方法是卸载它所驻留的应用程序域。

沃森博士就在这里。

从Visual Studio 2015中的编译器运行并使用“诊断工具”窗口,大约38秒后显示以下结果。请注意,进程内存在稳步上升,垃圾收集器(GC)仍在运行,但无法收集任何东西。

诊断工具窗口


2

C 30字节

f(){int *i=malloc(sizeof(4));}

Valgrind结果:

         ==26311== HEAP SUMMARY:
         ==26311==     in use at exit: 4 bytes in 1 blocks
         ==26311==   total heap usage: 1 allocs, 0 frees, 4 bytes allocated
         ==26311== 
         ==26311== LEAK SUMMARY:
         ==26311==    definitely lost: 4 bytes in 1 blocks
         ==26311==    indirectly lost: 0 bytes in 0 blocks
         ==26311==      possibly lost: 0 bytes in 0 blocks
         ==26311==    still reachable: 0 bytes in 0 blocks
         ==26311==         suppressed: 0 bytes in 0 blocks
         ==26311== Rerun with --leak-check=full to see details of leaked memory

2
可以代替做main(){malloc(1);}吗?
kirbyfan64sos

@是的!但是它已经发布了!
亚伯汤姆

2

Dart,76个字节

import'dart:async';main()=>new Stream.periodic(Duration.ZERO).listen((_){});

有点像JavaScript的答案。当您调用.listenDart流对象时,将获得一个StreamSubscription,它使您可以与流断开连接。但是,如果丢掉它,则永远无法取消订阅该流,从而导致泄漏。解决泄漏的唯一方法是收集Stream本身,但仍由StreamController + Timer组合在内部引用。

不幸的是,对于我尝试过的其他东西,Dart太聪明了。()async=>await new Completer().future不起作用,因为使用await与doing相同new Completer().future.then(<continuation>),它允许在未引用第二个Completer的情况下关闭闭包本身(Completer保留了对Future的引用.future Future的引用,Future保留了对作为闭包的延续的引用)。

另外,隔离(aka线程)由GC清除,因此在新线程中产生自己并立即暂停(import'dart:isolate';main(_)=>Isolate.spawn(main,0,paused:true);)不起作用。即使生成带有无限循环(import'dart:isolate';f(_){while(true){print('x');}}main()=>Isolate.spawn(f,0);)的隔离,也会杀死该隔离并退出程序。

那好吧。


如果您的主程序继续运行并执行其他操作,那么垃圾收集器是否能够停止隔离?我问,因为我的goroutine示例听起来很相似...我以为程序退出并将所有内存返回给OS的事实并不一定意味着它没有泄漏。
不要光明

2

Swift,12个字节

[3,5][0...0]

说明:

这是事实上的内存泄漏,无论该语言使用内存手动管理,自动引用计数(ARC,如Swift)还是什至清除垃圾回收,任何一种语言都可能发生。

[3,5]只是一个数组文字。该数组至少为这两个元素分配足够的内存。该35只是随意。

下标(编制索引)Array<T>会产生ArraySlice<T>。An ArraySlice<T>是对其创建数组的内存的视图。

[3,5][0...0]产生一个ArraySlice<Int>,其值为[3]。请注意,3此切片中的3元素与上面显示3的原始元素相同Array而不是副本。

然后可以将结果切片存储到变量中并使用。原始数组不再被引用,因此您认为可以将其释放。但是,它不能。

由于切片将视图公开到来自其的阵列的内存中,因此只要切片存在,原始阵列就必须保持活动状态。因此,在2分配的原始元素大小的内存中,仅使用第一个元素大小的内存,而另一个则需要存在,以便不分配第一个。内存的第二个元素大小被解因泄漏。

解决此问题的方法是不要使大型阵列的小片保持很长时间。如果需要保留切片内容,则将其提升为数组,这将触发要复制的内存,从而消除了对原始数组内存的依赖:

Array([3,5][0...0])

2

解决方案1:C(Mac OS X x86_64),109字节

golf_sol1.c的来源

main[]={142510920,2336753547,3505849471,284148040,2370322315,2314740852,1351437506,1208291319,914962059,195};

上面的程序需要使用__DATA段上的执行访问权限进行编译。

clang golf_sol1.c -o golf_sol1 -Xlinker -segprot -Xlinker __DATA -Xlinker rwx -Xlinker rwx

然后,要执行该程序,请运行以下命令:

./golf_sol1 $(ruby -e 'puts "\xf5\xff\xff\xfe\xff\xff\x44\x82\x57\x7d\xff\x7f"')

结果:

不幸的是,Valgrind不会监视从系统调用分配的内存,因此我无法显示出很好的检测到的泄漏。

但是,我们可以查看vmmap来查看分配的内存(MALLOC元数据)的大块。

                                VIRTUAL   REGION 
REGION TYPE                        SIZE    COUNT (non-coalesced) 
===========                     =======  ======= 
Kernel Alloc Once                    4K        2 
MALLOC guard page                   16K        4 
MALLOC metadata                   16.2M        7 
MALLOC_SMALL                      8192K        2         see MALLOC ZONE table below
MALLOC_TINY                       1024K        2         see MALLOC ZONE table below
STACK GUARD                       56.0M        2 
Stack                             8192K        3 
VM_ALLOCATE (reserved)             520K        3         reserved VM address space (unallocated)
__DATA                             684K       42 
__LINKEDIT                        70.8M        4 
__TEXT                            5960K       44 
shared memory                        8K        3 
===========                     =======  ======= 
TOTAL                            167.0M      106 
TOTAL, minus reserved VM space   166.5M      106 

说明

因此,我认为在介绍改进的解决方案之前,我需要描述一下这里实际发生的情况。

这个主要功能是滥用C的缺失类型声明(因此它默认为int,而无需我们浪费字符编写它),以及符号的工作方式。链接器仅关心是否可以找到main要调用的符号。因此,在这里我们将main做成一个int数组,并使用将要执行的shellcode对其进行初始化。因此,不会将main添加到__TEXT段,而是添加到__DATA段,原因是我们需要使用可执行的__DATA段来编译程序。

在main中找到的shellcode如下:

movq 8(%rsi), %rdi
movl (%rdi), %eax
movq 4(%rdi), %rdi
notl %eax
shrq $16, %rdi
movl (%rdi), %edi
leaq -0x8(%rsp), %rsi
movl %eax, %edx
leaq -9(%rax), %r10
syscall
movq (%rsi), %rsi
movl %esi, (%rsi)
ret

这是在调用syscall函数来分配内存页面(syscall mach_vm_allocate在内部使用)。RAX应该等于0x100000a(告诉系统调用我们想要的功能),而RDI持有分配目标(在我们的情况下,我们希望它是mach_task_self()),RSI应该持有地址以将指针写入到新创建的内存中(因此,我们只是将其指向堆栈的某个部分),RDX保留分配的大小(我们只是传递RAX或0x100000a只是为了节省字节数),R10保留标志(我们表明它可以分配到任何地方)。

现在还不清楚RA​​X和RDI从何处获取价值。我们知道RAX必须为0x100000a,RDI必须为mach_task_self()返回的值。幸运的是,mach_task_self()实际上是变量(mach_task_self_)的宏,该变量每次都位于相同的内存地址(但是应在重新启动时更改)。在我的特定情况下,mach_task_self_恰好位于0x00007fff7d578244。因此,为了减少指令,我们将改为从argv传递此数据。这就是为什么我们使用此表达式运行程序$(ruby -e 'puts "\xf5\xff\xff\xfe\xff\xff\x44\x82\x57\x7d\xff\x7f"')第一个论点。字符串是两个值的组合,其中RAX值(0x100000a)只有32位,并且对其应用了一个补码(因此没有空字节;我们没有获取原始值的值),下一个值是RDI(0x00007fff7d578244)已移至左侧,并在末尾添加了2个额外的垃圾字节(再次排除空字节,我们只是将其移回右侧以将其恢复为原始字节)。

进行系统调用后,我们正在写入新分配的内存。原因是因为使用mach_vm_allocate(或此syscall)分配的内存实际上是VM页面,并且不会自动分页到内存中。而是保留它们,直到将数据写入它们,然后将这些页面映射到内存中。如果只保留它,不确定是否满足要求。

对于下一个解决方案,我们将利用我们的shellcode没有空字节这一事实,因此可以将其移出程序代码之外以减小大小。

解决方案2:C(Mac OS X x86_64),44字节

golf_sol2.c的来源

main[]={141986632,10937,1032669184,2,42227};

上面的程序需要使用__DATA段上的执行访问权限进行编译。

clang golf_sol2.c -o golf_sol2 -Xlinker -segprot -Xlinker __DATA -Xlinker rwx -Xlinker rwx

然后,要执行该程序,请运行以下命令:

./golf_sol2 $(ruby -e 'puts "\xb8\xf5\xff\xff\xfe\xf7\xd0\x48\xbf\xff\xff\x44\x82\x57\x7d\xff\x7f\x48\xc1\xef\x10\x8b\x3f\x48\x8d\x74\x24\xf8\x89\xc2\x4c\x8d\x50\xf7\x0f\x05\x48\x8b\x36\x89\x36\xc3"')

结果应该与之前相同,因为我们正在分配相同的大小。

说明

遵循与解决方案1大致相同的概念,不同之处在于,我们已将泄漏的代码块移至程序外部。

现在在main中找到的shellcode如下:

movq 8(%rsi), %rsi
movl $42, %ecx
leaq 2(%rip), %rdi
rep movsb (%rsi), (%rdi)

这基本上是将我们传入argv的shellcode复制到此代码之后(因此,在复制完之后,它将运行插入的shellcode)。对我们有利的是__DATA段至少应为页面大小,因此即使我们的代码不是那么大,我们仍然可以“安全”地编写更多代码。缺点是这里的理想解决方案,甚至不需要复制,而是直接在argv中调用并执行shellcode。但不幸的是,该内存没有执行权限。我们可以更改此内存的权限,但是与复制内存相比,它需要更多的代码。另一种策略是从外部程序更改权利(但稍后会进行更多说明)。

我们传递给argv的shellcode如下:

movl $0xfefffff5, %eax
notl %eax
movq $0x7fff7d578244ffff, %rdi
shrq $16, %rdi
movl (%rdi), %edi
leaq -0x8(%rsp), %rsi
movl %eax, %edx
leaq -9(%rax), %r10
syscall
movq (%rsi), %rsi
movl %esi, (%rsi)
ret

这与我们之前的代码非常相似,唯一的区别是我们直接包含了EAX和RDI的值。

可能的解决方案1:C(Mac OS X x86_64),11字节

从外部修改程序的想法为我们提供了将泄漏程序移至外部程序的可能解决方案。我们的实际程序(提交)只是一个伪程序,而泄漏程序将在目标程序中分配一些内存。现在,我不确定这是否会属于此挑战的规则,但还是要分享它。

因此,如果我们在目标设置为挑战程序的外部程序中使用mach_vm_allocate,那可能意味着我们的挑战程序只需要遵循以下内容:

main=65259;

该shellcode只是对自身的短暂跳转(无限跳转/循环),因此程序保持打开状态,我们可以从外部程序中引用它。

可能的解决方案2:C(Mac OS X x86_64),8字节

有趣的是,当我查看valgrind输出时,至少看到valgrind认为dyld会泄漏内存。因此,每个程序实际上都在泄漏一些内存。在这种情况下,我们实际上可以制作一个不执行任何操作(仅退出)的程序,而该程序实际上会泄漏内存。

资源:

main(){}


==55263== LEAK SUMMARY:
==55263==    definitely lost: 696 bytes in 17 blocks
==55263==    indirectly lost: 17,722 bytes in 128 blocks
==55263==      possibly lost: 0 bytes in 0 blocks
==55263==    still reachable: 0 bytes in 0 blocks
==55263==         suppressed: 16,316 bytes in 272 blocks

2

普通英语71 70 58 35字节

通过删除空白行删除了1个字节。通过消除“ bogon”类型定义,并使用父“ thing”类型而不是“ bogon”子类型,删除了12个字节。通过从一个完整的程序切换到一个泄漏内存的例程,删除了23个字节。

高尔夫球版:

To x:
Allocate memory for a thing.

是完整程序的非公开版本,使用子类型定义,并且不会泄漏内存:

A bogon is a thing.

To do something:
  Allocate memory for a bogon.
  Destroy the bogon.

To run:
  Start up.
  Do something.
  Shut down.

如果调用了“ x”的高尔夫球版本,则它将与调用“ x”的次数成比例地泄漏内存。在高尔夫版本中,“解除分配”。将修复内存泄漏。

默认情况下,纯英语检查内存泄漏。运行泄漏内存的版本时,将在程序关闭之前显示一个对话框。该对话框的标题为“调试”,消息为“ 1滴”和“确定”按钮。泄漏函数被调用的次数越多,消息中“漂移”的数量就越大。运行不泄漏内存的版本时,不会出现对话框。

用简单的英语来说,“事物”是指向双向链接列表中项目的指针。“事物”,“启动”和“关闭”在称为“面条”的模块中定义,需要将其复制到(通常作为单独的文件)到每个项目中。在编译器中定义了“ A”,“ the”,“ to”,“分配内存”和“销毁”。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.