mirror of
https://github.com/openssh/libopenssh
synced 2026-04-16 01:35:52 +00:00
Merge branch 'master' of /home/djm/cvs/leakmalloc into merge-leakmalloc
This commit is contained in:
19
leakmalloc/README
Normal file
19
leakmalloc/README
Normal file
@@ -0,0 +1,19 @@
|
||||
This is a very simple memory leak detector using libexecinfo (in
|
||||
OpenBSD ports as devel/libexecinfo). It could be adapted to use the
|
||||
backtrace function in glibc too.
|
||||
|
||||
It tracks memory allocations by redirecting malloc/free and friends
|
||||
to its own functions using preprocessor defines. When an allocation
|
||||
is made, leakmalloc grabs the call chain from the stack and stores it.
|
||||
When the program exits, leakmalloc dumps all unfreed allocations.
|
||||
|
||||
leakresolve.py consumes one of these dumps and produces a fairly
|
||||
readable report with leaks coalesced by stack trace and sorted by
|
||||
number of leak instances. Usually the last entry on this report is
|
||||
the one to care most about.
|
||||
|
||||
leaky.c in regress/ shows how you might use it (run 'make' in that directory
|
||||
and run it manually, or look at the Makefile for tips).
|
||||
|
||||
Note that everything needs to be compiled without optimisation and with
|
||||
debugging symbols.
|
||||
28
leakmalloc/leakmalloc/Makefile
Normal file
28
leakmalloc/leakmalloc/Makefile
Normal file
@@ -0,0 +1,28 @@
|
||||
# $OpenBSD$
|
||||
|
||||
LIB= leakmalloc
|
||||
SRCS= leakmalloc.c
|
||||
HDRS= leakmalloc.h
|
||||
#MAN= leakmalloc.3
|
||||
NOMAN=1
|
||||
NOPIC=1
|
||||
NOPROFILE=1
|
||||
|
||||
CDIAGFLAGS= -Wall
|
||||
CDIAGFLAGS+= -Werror
|
||||
CDIAGFLAGS+= -Wstrict-prototypes
|
||||
CDIAGFLAGS+= -Wmissing-prototypes
|
||||
CDIAGFLAGS+= -Wmissing-declarations
|
||||
CDIAGFLAGS+= -Wshadow
|
||||
CDIAGFLAGS+= -Wpointer-arith
|
||||
CDIAGFLAGS+= -Wcast-qual
|
||||
CDIAGFLAGS+= -Wsign-compare
|
||||
CDIAGFLAGS+= -Wcast-align
|
||||
CDIAGFLAGS+= -Wbad-function-cast
|
||||
|
||||
CPPFLAGS+= -I/usr/local/include
|
||||
DEBUG=-ggdb3
|
||||
COPTS=-O0
|
||||
INSTALL_STRIP=
|
||||
|
||||
.include <bsd.lib.mk>
|
||||
229
leakmalloc/leakmalloc/leakmalloc.c
Normal file
229
leakmalloc/leakmalloc/leakmalloc.c
Normal file
@@ -0,0 +1,229 @@
|
||||
/*
|
||||
* Copyright (c) 2012 Damien Miller <djm@mindrot.org>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/tree.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <err.h>
|
||||
|
||||
#include <execinfo.h>
|
||||
|
||||
#define LEAKMALLOC_NO_REDIRECT
|
||||
#include "leakmalloc.h"
|
||||
|
||||
#define OPT_EXIT_ON_LEAKS 0x01
|
||||
#define OPT_DUMP_TO_FILE 0x02
|
||||
#define OPT_QUIET 0x04
|
||||
|
||||
u_int leakmalloc_options = 0;
|
||||
static int initialised;
|
||||
static FILE *dumpfile;
|
||||
|
||||
struct alloc {
|
||||
RB_ENTRY(alloc) entry;
|
||||
void *addr;
|
||||
size_t len;
|
||||
void *bt[BT_MAX_DEPTH];
|
||||
int depth;
|
||||
};
|
||||
|
||||
static int
|
||||
alloc_cmp(struct alloc *a, struct alloc *b)
|
||||
{
|
||||
if (a->addr == b->addr)
|
||||
return 0;
|
||||
if (a->addr > b->addr)
|
||||
return 1;
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
RB_HEAD(alloc_tree, alloc);
|
||||
RB_GENERATE_STATIC(alloc_tree, alloc, entry, alloc_cmp);
|
||||
static struct alloc_tree alloc_tree = RB_INITIALIZER(&alloc_tree);
|
||||
|
||||
static void
|
||||
dump_leak(FILE *f, const char *tag, struct alloc *alloc)
|
||||
{
|
||||
int i;
|
||||
|
||||
fprintf(f, "%s %p %zu TRACE", tag, alloc->addr, alloc->len);
|
||||
for (i = 1; i < alloc->depth; i++)
|
||||
fprintf(f, " %p", alloc->bt[i]);
|
||||
fprintf(f, "\n");
|
||||
}
|
||||
|
||||
/* Called atexit to dump unfreed leak objects */
|
||||
static void
|
||||
dump_leaks(void)
|
||||
{
|
||||
struct alloc *alloc;
|
||||
int i = 0;
|
||||
|
||||
if (initialised != 1)
|
||||
return;
|
||||
RB_FOREACH(alloc, alloc_tree, &alloc_tree) {
|
||||
if ((leakmalloc_options & OPT_QUIET) == 0)
|
||||
dump_leak(dumpfile ? dumpfile : stdout, "LEAK", alloc);
|
||||
i++;
|
||||
}
|
||||
if (dumpfile)
|
||||
fclose(dumpfile);
|
||||
if ((leakmalloc_options & OPT_EXIT_ON_LEAKS) != 0)
|
||||
_exit(99);
|
||||
}
|
||||
|
||||
static void
|
||||
internal_error(struct alloc *alloc)
|
||||
{
|
||||
alloc->addr = NULL;
|
||||
alloc->len = -1;
|
||||
dump_leak(stderr, "ERROR", alloc);
|
||||
initialised = -1;
|
||||
}
|
||||
|
||||
static struct alloc *
|
||||
new_alloc(void *addr, size_t len)
|
||||
{
|
||||
struct alloc *alloc;
|
||||
|
||||
if ((alloc = calloc(1, sizeof(*alloc))) == NULL)
|
||||
errx(1, "%s: calloc failed", __func__);
|
||||
alloc->addr = addr;
|
||||
alloc->len = len;
|
||||
if ((alloc->depth = backtrace(alloc->bt, BT_MAX_DEPTH)) == -1)
|
||||
errx(1, "%s: backtrace failed", __func__);
|
||||
return alloc;
|
||||
}
|
||||
|
||||
static void
|
||||
__record_leak(void *addr, size_t len, void *oaddr)
|
||||
{
|
||||
struct alloc oalloc, *alloc;
|
||||
char *cp;
|
||||
|
||||
if (initialised == -1)
|
||||
return;
|
||||
else if (initialised == 0) {
|
||||
atexit(dump_leaks);
|
||||
if (!issetugid() &&
|
||||
(cp = getenv("LEAKMALLOC_OPTIONS")) != NULL) {
|
||||
if (strchr(cp, 'X') != NULL)
|
||||
leakmalloc_options |= OPT_EXIT_ON_LEAKS;
|
||||
if (strchr(cp, 'D') != NULL)
|
||||
leakmalloc_options |= OPT_DUMP_TO_FILE;
|
||||
if (strchr(cp, 'Q') != NULL)
|
||||
leakmalloc_options |= OPT_QUIET;
|
||||
}
|
||||
if ((leakmalloc_options & OPT_DUMP_TO_FILE) != 0 &&
|
||||
(dumpfile = fopen("leakmalloc.out", "w+")) == NULL)
|
||||
err(1, "fopen(\"leakmalloc.out\")");
|
||||
initialised = 1;
|
||||
}
|
||||
|
||||
if (oaddr == NULL) {
|
||||
/*
|
||||
* malloc/calloc/realloc(NULL,...): allocate a leak object
|
||||
* and fill in the trace.
|
||||
*/
|
||||
if (addr == NULL)
|
||||
return; /* alloc failed or free(NULL) */
|
||||
alloc = new_alloc(addr, len);
|
||||
if (RB_INSERT(alloc_tree, &alloc_tree, alloc) != NULL) {
|
||||
internal_error(alloc);
|
||||
errx(1, "%s: alloc for %p already exists",
|
||||
__func__, addr);
|
||||
}
|
||||
} else {
|
||||
oalloc.addr = oaddr;
|
||||
alloc = RB_FIND(alloc_tree, &alloc_tree, &oalloc);
|
||||
if (addr == NULL) {
|
||||
/*
|
||||
* free: delete the tracked leak.
|
||||
*/
|
||||
if (alloc == NULL)
|
||||
return; /* Ignore untracked memory */
|
||||
RB_REMOVE(alloc_tree, &alloc_tree, alloc);
|
||||
free(alloc);
|
||||
} else {
|
||||
/*
|
||||
* realloc: update the original address so we can
|
||||
* trace it when it is freed.
|
||||
*/
|
||||
if (alloc == NULL) {
|
||||
alloc = new_alloc(NULL, -1);
|
||||
internal_error(alloc);
|
||||
errx(1, "%s: realloc original addr %p missing",
|
||||
__func__, oaddr);
|
||||
}
|
||||
RB_REMOVE(alloc_tree, &alloc_tree, alloc);
|
||||
alloc->addr = addr;
|
||||
alloc->len = len;
|
||||
if (RB_INSERT(alloc_tree, &alloc_tree, alloc) != NULL) {
|
||||
internal_error(alloc);
|
||||
errx(1, "%s: alloc for %p already exists",
|
||||
__func__, addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
char *
|
||||
leak_strdup(const char *s)
|
||||
{
|
||||
char *ret = strdup(s);
|
||||
|
||||
__record_leak(ret, ret == NULL ? 0 : strlen(ret), NULL);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void *
|
||||
leak_malloc(size_t len)
|
||||
{
|
||||
void *ret = malloc(len);
|
||||
|
||||
__record_leak(ret, len, NULL);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void *
|
||||
leak_calloc(size_t nmemb, size_t size)
|
||||
{
|
||||
void *ret = calloc(nmemb, size);
|
||||
|
||||
__record_leak(ret, nmemb * size, NULL);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void *
|
||||
leak_realloc(void *s, size_t len)
|
||||
{
|
||||
void *ret = realloc(s, len);
|
||||
|
||||
__record_leak(ret, len, s);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void
|
||||
leak_free(void *s)
|
||||
{
|
||||
free(s);
|
||||
__record_leak(NULL, 0, s);
|
||||
}
|
||||
|
||||
39
leakmalloc/leakmalloc/leakmalloc.h
Normal file
39
leakmalloc/leakmalloc/leakmalloc.h
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (c) 2012 Damien Miller <djm@mindrot.org>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef _LEAKMALLOC_H
|
||||
#define _LEAKMALLOC_H
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
char *leak_strdup(const char *s);
|
||||
void *leak_malloc(size_t len);
|
||||
void *leak_calloc(size_t nmemb, size_t size);
|
||||
void *leak_realloc(void *s, size_t len);
|
||||
void leak_free(void *s);
|
||||
|
||||
#ifndef LEAKMALLOC_NO_REDIRECT
|
||||
#define malloc leak_malloc
|
||||
#define strdup leak_strdup
|
||||
#define calloc leak_calloc
|
||||
#define realloc leak_realloc
|
||||
#define free leak_free
|
||||
#endif /* LEAKMALLOC_NO_REDIRECT */
|
||||
|
||||
#endif /* _LEAKMALLOC_H */
|
||||
|
||||
129
leakmalloc/leakresolve.py
Executable file
129
leakmalloc/leakresolve.py
Executable file
@@ -0,0 +1,129 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (c) 2012 Damien Miller <djm@mindrot.org>
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software for any
|
||||
# purpose with or without fee is hereby granted, provided that the above
|
||||
# copyright notice and this permission notice appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
# Resolve leak dump to backtraces
|
||||
|
||||
import sys
|
||||
import getopt
|
||||
import subprocess
|
||||
|
||||
TRIM_TRACE=True
|
||||
|
||||
def usage():
|
||||
print >> sys.stderr, "leakresolve.py -p executable < trace"
|
||||
sys.exit(1);
|
||||
|
||||
class LineResolver:
|
||||
"""Resolves addresses to source lines"""
|
||||
def __init__(self, executable):
|
||||
self.resolver = subprocess.Popen(
|
||||
["addr2line", "-e", executable, '-C', '-f'],
|
||||
bufsize=1, # line-buffered
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
)
|
||||
self.cache = {}
|
||||
def resolve(self, addr):
|
||||
if addr not in self.cache:
|
||||
self.resolver.stdin.write(addr + "\n")
|
||||
func = self.resolver.stdout.readline()
|
||||
loc = self.resolver.stdout.readline()
|
||||
result = "%s: in %s()" % (loc.strip(), func.strip())
|
||||
self.cache[addr] = result
|
||||
return self.cache[addr]
|
||||
|
||||
class Leak:
|
||||
"""Represents a memory leak site"""
|
||||
def __init__(self, backtrace, resolver):
|
||||
self.backtrace = backtrace;
|
||||
self.resolver = resolver
|
||||
self.nleaks = 0
|
||||
self.nbytes = 0;
|
||||
def leak(self, nbytes):
|
||||
self.nbytes += nbytes
|
||||
self.nleaks += 1
|
||||
def __str__(self):
|
||||
s = "Leaked %d objects totalling %d bytes\n" % \
|
||||
(self.nleaks, self.nbytes)
|
||||
s += "\n".join(map(self.resolver.resolve, self.backtrace))
|
||||
return s
|
||||
|
||||
class LeakTracker:
|
||||
"""Tracks all memory leaks"""
|
||||
def __init__(self, executable):
|
||||
self.resolver = LineResolver(executable)
|
||||
self.leaks = {}
|
||||
def addleak(self, nbytes, trace):
|
||||
if TRIM_TRACE:
|
||||
trace = trace[:-1]
|
||||
trace = tuple(trace)
|
||||
if trace not in self.leaks:
|
||||
leak = Leak(trace, self.resolver)
|
||||
self.leaks[trace] = leak
|
||||
self.leaks[trace].leak(nbytes)
|
||||
def _leakcmp(self, a, b):
|
||||
r = cmp(self.leaks[a].nleaks, self.leaks[b].nleaks)
|
||||
if r:
|
||||
return r
|
||||
return cmp(self.leaks[a].nbytes, self.leaks[b].nbytes)
|
||||
def __str__(self):
|
||||
s = "Memory leaks\n"
|
||||
s+= "------------\n"
|
||||
total_sites = 0
|
||||
total_leaks = 0
|
||||
total_bytes = 0
|
||||
for trace in sorted(self.leaks.keys(), cmp=self._leakcmp):
|
||||
s += str(self.leaks[trace]) + "\n\n"
|
||||
total_sites += 1
|
||||
total_leaks += self.leaks[trace].nleaks
|
||||
total_bytes += self.leaks[trace].nbytes
|
||||
s+= "Total: %d leaks from %d sites, containing %d bytes\n" % \
|
||||
(total_leaks, total_sites, total_bytes)
|
||||
return s
|
||||
|
||||
def main():
|
||||
executable = None
|
||||
try:
|
||||
opts, args = getopt.getopt(sys.argv[1:], 'hp:')
|
||||
except getopt.GetoptError:
|
||||
print >> sys.stderr, "Invalid commandline arguments"
|
||||
usage()
|
||||
for o, a in opts:
|
||||
if o in ('-h', '--help'):
|
||||
usage()
|
||||
if o in ('-p', '--program'):
|
||||
executable = a
|
||||
continue
|
||||
|
||||
if not executable:
|
||||
print >> sys.stderr, "Missing executable name"
|
||||
usage();
|
||||
|
||||
leaks = LeakTracker(executable)
|
||||
for line in sys.stdin:
|
||||
if line.startswith("LEAK "):
|
||||
leakinfo = line.split()
|
||||
if len(leakinfo) < 4 or leakinfo[3] != "TRACE":
|
||||
sys.stdout.write(line);
|
||||
continue
|
||||
backtrace = leakinfo[4:]
|
||||
nbytes = int(leakinfo[2])
|
||||
leaks.addleak(nbytes, backtrace)
|
||||
|
||||
print leaks
|
||||
|
||||
if __name__ == '__main__': main()
|
||||
|
||||
57
leakmalloc/regress/Makefile
Normal file
57
leakmalloc/regress/Makefile
Normal file
@@ -0,0 +1,57 @@
|
||||
# $OpenBSD$
|
||||
|
||||
.include <bsd.own.mk>
|
||||
.include <bsd.obj.mk>
|
||||
|
||||
CDIAGFLAGS= -Wall
|
||||
CDIAGFLAGS+= -Werror
|
||||
CDIAGFLAGS+= -Wpointer-arith
|
||||
CDIAGFLAGS+= -Wstrict-prototypes
|
||||
CDIAGFLAGS+= -Wmissing-prototypes
|
||||
CDIAGFLAGS+= -Wunused
|
||||
CDIAGFLAGS+= -Wsign-compare
|
||||
CDIAGFLAGS+= -Wshadow
|
||||
CDIAGFLAGS+= -Wformat
|
||||
.if (${CC:L} == "gcc" || ${CC:L} == "cc")
|
||||
CDIAGFLAGS+= -Wbounded
|
||||
.endif
|
||||
DEBUG=-g
|
||||
COPTS=-O0
|
||||
|
||||
CPPFLAGS+=-I${.CURDIR}/../leakmalloc
|
||||
|
||||
.if exists(${.CURDIR}/../leakmalloc/${__objdir})
|
||||
LDADD+=-L${.CURDIR}/../leakmalloc/${__objdir} -lleakmalloc
|
||||
DPADD+=${.CURDIR}/../leakmalloc/${__objdir}/libleakmalloc.a
|
||||
.else
|
||||
LDADD+=-L${.CURDIR}/../leakmalloc -lleakmalloc
|
||||
DPADD+=${.CURDIR}/../leakmalloc/libleakmalloc.a
|
||||
.endif
|
||||
|
||||
LDFLAGS+=-L/usr/local/lib
|
||||
LDADD+=-lexecinfo
|
||||
|
||||
PROG=leaky
|
||||
SRCS=leaky.c
|
||||
REGRESS_TARGETS=does-leak outfile exit-leak summary
|
||||
|
||||
does-leak: ${PROG}
|
||||
./${PROG} | grep -q "^LEAK "
|
||||
|
||||
outfile: ${PROG}
|
||||
rm -f leakmalloc.out
|
||||
env LEAKMALLOC_OPTIONS=D ./${PROG}
|
||||
grep -q "^LEAK " leakmalloc.out
|
||||
|
||||
exit-leak: ${PROG}
|
||||
sh -c 'export LEAKMALLOC_OPTIONS=XQ ; ./${PROG} ; test $$? -eq 99'
|
||||
|
||||
summary: ${PROG}
|
||||
test `./${PROG} | ${.CURDIR}/../leakresolve.py -p ${PROG} | \
|
||||
grep "^Total:" | cut -d' ' -f 2` -eq 22
|
||||
|
||||
clean:
|
||||
rm ${PROG} *.o *.core leakmalloc.out
|
||||
|
||||
.include <bsd.regress.mk>
|
||||
|
||||
60
leakmalloc/regress/leaky.c
Normal file
60
leakmalloc/regress/leaky.c
Normal file
@@ -0,0 +1,60 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "leakmalloc.h"
|
||||
|
||||
static void *a, *b, *c, *d, *e, *f;
|
||||
|
||||
static void
|
||||
f6(void)
|
||||
{
|
||||
d = strdup("hello");
|
||||
e = malloc(789);
|
||||
}
|
||||
|
||||
static void
|
||||
f5(void)
|
||||
{
|
||||
c = calloc(1, 678);
|
||||
f6();
|
||||
}
|
||||
|
||||
static void
|
||||
f4(void)
|
||||
{
|
||||
b = malloc(456);
|
||||
f5();
|
||||
free(e);
|
||||
}
|
||||
|
||||
static void
|
||||
f3(void)
|
||||
{
|
||||
a = malloc(123);
|
||||
f = realloc(NULL, 321);
|
||||
}
|
||||
|
||||
static void
|
||||
f2(void)
|
||||
{
|
||||
f4();
|
||||
b = realloc(b, 567);
|
||||
}
|
||||
|
||||
static void
|
||||
f1(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
f2();
|
||||
for (i = 0; i < 10; i++)
|
||||
f3();
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
f1();
|
||||
free(f);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user