memory and subroutines

F

Frank Silvermann

#include <stdio.h>
#include <stdlib.h>
int * subroutine(int);
int main(void)
{
int n = 7, i;
int * p;
p = subroutine(n);
printf("in main\n");
for(i=0;i<n;i++)
{
printf("%d %d\n", i, p);
}
free(p);
return 0;
}

int * subroutine(int n)
{
int i;
int * q;
q = malloc(n*sizeof(int));
for(i=0;i<n;i++)
{
q = i;
printf("%d %d\n", i, q);
}
return q;
}
/* end source */
Besides not checking the malloc call for trouble, does this look like a
sound method to call a subroutine that allocates memory, puts something
into that memory, and then makes that data available to main? frank
 
E

Eric Sosman

Frank Silvermann wrote On 06/14/06 10:57,:
#include <stdio.h>
#include <stdlib.h>
int * subroutine(int);
int main(void)
{
int n = 7, i;
int * p;
p = subroutine(n);
printf("in main\n");
for(i=0;i<n;i++)
{
printf("%d %d\n", i, p);
}
free(p);
return 0;
}

int * subroutine(int n)
{
int i;
int * q;
q = malloc(n*sizeof(int));
for(i=0;i<n;i++)
{
q = i;
printf("%d %d\n", i, q);
}
return q;
}
/* end source */
Besides not checking the malloc call for trouble, does this look like a
sound method to call a subroutine that allocates memory, puts something
into that memory, and then makes that data available to main? frank


Looks fine. The malloc() call could be written a
little more stylishly as `q = malloc(n * sizeof *q);',
which carries a certain amount of self-verification.

Also, when writing a function that our C++ friends
might call a "constructor," consider writing a companion
"destructor" function at the same time. In this case the
companion would simply call free(), but other uses may
eventually appear and you'll be prepared for them. For
example, you might decide that the program creates and
destroys so many of these things that it's worth while to
implement a little cache instead of thrashing back and
forth so much with malloc() and free(). This will be easy
to do if the "clients" call your destructor function, much
harder if they're in the habit of calling free() directly.
Even a question like "What is the maximum number of these
things the program ever uses at any one time?" is easy
to answer if you've followed the constructor/destructor
pattern, hard if you can't "see both ends" of a thing's
lifetime.
 
F

Frank Silvermann

Eric said:
Frank Silvermann wrote On 06/14/06 10:57,:
#include <stdio.h>
#include <stdlib.h>
int * subroutine(int);
int main(void)
{
int n = 7, i;
int * p;
p = subroutine(n);
printf("in main\n");
for(i=0;i<n;i++)
{
printf("%d %d\n", i, p);
}
free(p);
return 0;
}

int * subroutine(int n)
{
int i;
int * q;
q = malloc(n*sizeof(int));
for(i=0;i<n;i++)
{
q = i;
printf("%d %d\n", i, q);
}
return q;
}
/* end source */
Besides not checking the malloc call for trouble, does this look like a
sound method to call a subroutine that allocates memory, puts something
into that memory, and then makes that data available to main? frank


Looks fine. The malloc() call could be written a
little more stylishly as `q = malloc(n * sizeof *q);',
which carries a certain amount of self-verification.

I understand that this is more robust. I guess I stuck with sizeof int
to address my own (weak) understanding during the debugging campaign.
Also, when writing a function that our C++ friends
might call a "constructor," consider writing a companion
"destructor" function at the same time. In this case the
companion would simply call free(), but other uses may
eventually appear and you'll be prepared for them. For
example, you might decide that the program creates and
destroys so many of these things that it's worth while to
implement a little cache instead of thrashing back and
forth so much with malloc() and free(). This will be easy
to do if the "clients" call your destructor function, much
harder if they're in the habit of calling free() directly.
Even a question like "What is the maximum number of these
things the program ever uses at any one time?" is easy
to answer if you've followed the constructor/destructor
pattern, hard if you can't "see both ends" of a thing's
lifetime.
I see your point. Here I might put the output in a function:
void print_it_and_free_it{char * p, n)
{
for(i=0;i<n;i++)
{
printf("%d %d\n", i, p);
}
free(p);
/* no need to return anything */
}
I'm a little sketchier on what a cache would mean in this context. frank
 
E

Eric Sosman

Frank Silvermann wrote On 06/14/06 15:57,:
Eric Sosman wrote:
[...]
Also, when writing a function that our C++ friends
might call a "constructor," consider writing a companion
"destructor" function at the same time. In this case the
companion would simply call free(), but other uses may
eventually appear and you'll be prepared for them. For
example, you might decide that the program creates and
destroys so many of these things that it's worth while to
implement a little cache instead of thrashing back and
forth so much with malloc() and free(). This will be easy
to do if the "clients" call your destructor function, much
harder if they're in the habit of calling free() directly.
Even a question like "What is the maximum number of these
things the program ever uses at any one time?" is easy
to answer if you've followed the constructor/destructor
pattern, hard if you can't "see both ends" of a thing's
lifetime.

I see your point. Here I might put the output in a function:
void print_it_and_free_it{char * p, n)

There's a fairly important design principle known as
KISS for "Keep It Simple, Stupid!" Try to get a function
to do one easily-described job and do it well; avoid writing
functions that resemble Swiss Army pocket knives with fifty-
leven folding blades. Here, I'd suggest writing a print_it()
function and a separate free_it() function; the two tasks
(printing and freeing) don't seem to have much to do with
each other, and probably don't belong together. What if you
someday want to print it, adjust it, print it again, and
then free it? What if you want to free it without printing?
You can build more different kinds of things out of the simple
shape of a brick than from the complex shape of a fireplace.

(This advice isn't really C-specific or even programming-
specific; it applies in a lot of endeavors. If anything, C
offers less "active help" to the programmer than some other
languages do, so there's a higher premium on simplicity as
an aid to understanding -- and error avoidance ...)
I'm a little sketchier on what a cache would mean in this context. frank

The example you gave probably wouldn't have much use for
a cache. (But then, the example didn't seem all that realistic:
How frequently do you find yourself needing an array of the
integers 0 through n-1?) But suppose you're writing something
like a chess-playing game that allocates lots and lots of data
structures to represent possible game positions. After it
ponders matters for a while, your program chooses one of the
available moves and thereby discards all the positions that
resulted from moves not chosen. Then the opponent chooses a
move and once again your program discards a lot of data, and
then you start exploring the new situation and re-allocating
all those position records again. Instead of diving in and
out of malloc() and free() all the time, you might find it
advantageous to "free" a position by just sticking it on a
list of position records that are currently unused and "blank."
Then when you need a new position you can recycle one of the
old ones, and only use malloc() when the supply of recyclables
runs dry.

Anyhow, it's far from unlikely that you'll find reasons to
want to do something special when objects are deallocated,
even if those reasons aren't apparent from the start. If you've
written a free_it() function, even if it does nothing but call
free() when you first write it, you'll have given yourself a
convenient place to add the special sauce later on. But if your
callers just call free() behind your back, ...
 
F

Frank Silvermann

Eric said:
Frank Silvermann wrote On 06/14/06 15:57,:
Eric Sosman wrote:
[...]
Also, when writing a function that our C++ friends
might call a "constructor," consider writing a companion
"destructor" function at the same time. In this case the
companion would simply call free(), but other uses may
eventually appear and you'll be prepared for them. For
example, you might decide that the program creates and
destroys so many of these things that it's worth while to
implement a little cache instead of thrashing back and
forth so much with malloc() and free(). This will be easy
to do if the "clients" call your destructor function, much
harder if they're in the habit of calling free() directly.
Even a question like "What is the maximum number of these
things the program ever uses at any one time?" is easy
to answer if you've followed the constructor/destructor
pattern, hard if you can't "see both ends" of a thing's
lifetime.
I see your point. Here I might put the output in a function:
void print_it_and_free_it{char * p, n)

There's a fairly important design principle known as
KISS for "Keep It Simple, Stupid!" Try to get a function
to do one easily-described job and do it well; avoid writing
functions that resemble Swiss Army pocket knives with fifty-
leven folding blades. Here, I'd suggest writing a print_it()
function and a separate free_it() function; the two tasks
(printing and freeing) don't seem to have much to do with
each other, and probably don't belong together. What if you
someday want to print it, adjust it, print it again, and
then free it? What if you want to free it without printing?
You can build more different kinds of things out of the simple
shape of a brick than from the complex shape of a fireplace.

(This advice isn't really C-specific or even programming-
specific; it applies in a lot of endeavors. If anything, C
offers less "active help" to the programmer than some other
languages do, so there's a higher premium on simplicity as
an aid to understanding -- and error avoidance ...)
I'm a little sketchier on what a cache would mean in this context. frank

The example you gave probably wouldn't have much use for
a cache. (But then, the example didn't seem all that realistic:
How frequently do you find yourself needing an array of the
integers 0 through n-1?) But suppose you're writing something
like a chess-playing game that allocates lots and lots of data
structures to represent possible game positions. After it
ponders matters for a while, your program chooses one of the
available moves and thereby discards all the positions that
resulted from moves not chosen. Then the opponent chooses a
move and once again your program discards a lot of data, and
then you start exploring the new situation and re-allocating
all those position records again. Instead of diving in and
out of malloc() and free() all the time, you might find it
advantageous to "free" a position by just sticking it on a
list of position records that are currently unused and "blank."
Then when you need a new position you can recycle one of the
old ones, and only use malloc() when the supply of recyclables
runs dry.

Anyhow, it's far from unlikely that you'll find reasons to
want to do something special when objects are deallocated,
even if those reasons aren't apparent from the start. If you've
written a free_it() function, even if it does nothing but call
free() when you first write it, you'll have given yourself a
convenient place to add the special sauce later on. But if your
callers just call free() behind your back, ...

Like an overeager apprentice, I have already gone and done what you told
me not to do. I can claim, at least for the duration of this post, that
I was acting in ignorance. The good news it that I've now got pointers
that get passed properly and data to examine. I think that if I permute
p, I'll have the random partition I want:


/* partition3.c */

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <math.h>
#define SWAP(m, n) (tmp = (m), (m) = (n), (n) = tmp)
#define ITERATIONS 15

int rand_in_range(int, int);
int * partition(int, int);
void print_it_and_kill_it(int * , int);

int main(void)
{
int m=15, n=6, i;
int *p;
/* all declarations in main need to be north of here */
/* seed srand with time */
srand(time(NULL));
/* make subroutine calls ITERATION times and examine returns */
printf("set has %d elements and %d partitions\n", m, n);
for(i = 0; i < ITERATIONS; i++)
{
p = partition(m,n);
print_it_and_kill_it(p,n);
}
return 0;
}


int rand_in_range(int m, int n)
{
/*seed srand in main */
/* [m, n] is range */
int roll_again_threshold, divisor, result, tmp, offset, num_results;

if (m>n) SWAP(m, n);
offset = m;
num_results = n - m + 1;

if (num_results == 1) {
return m;
}

roll_again_threshold = RAND_MAX - RAND_MAX%num_results;
divisor = roll_again_threshold/num_results;

do {
result = rand();
} while (result >= roll_again_threshold);
result /= divisor;
return offset + result;
}

int * partition(int m, int n)
{
int top_range, i, p;
int *q;
/* end declarations */
q = malloc((n)*sizeof(*q));
/* if n>m bomb out */
if (n > m) return NULL;
top_range = m - n;

/* control */
for (i=0; i<(n-1); i++)
{
p=rand_in_range(0, top_range);
q = p + 1;
top_range = top_range - p;
}
q[n-1]=top_range + 1;
return q;
}

void print_it_and_kill_it(int * p, int n)
{
int j;

for (j = 0;j < n; j++)
{
printf(" %d", p[j]);
}
printf("\n");
free(p);
}
/* end source */
I'll need to read your post as a hard copy and thank you explicitly for
it. frank
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,535
Members
45,007
Latest member
obedient dusk

Latest Threads

Top