[comp.lang.c] Portable Memory Allocation

herndon@umn-cs.UUCP (Robert Herndon) (06/09/87)

  I have an application where I want to represent nodes with
a finite, fixed number of possible fields, e.g., applications
equivalent to pascal's variant records.  Some of these nodes
will be relatively small, others large.  I would like to allocate
only enough memory for the union to hold the info necessary.
Is there any portable, COMPACT way to do this in C?

E.g., I have nodes which look like:
  struct node {
     struct this_stuff x;
     struct that_stuff y;
     struct othr_stuff z;
     union  var_part {
        struct childrenA a[27];
        struct childrenB b[3];
        struct childrenC c[42];
     } c;
  };

I would like to be able to make a call something like
    nodeptr = malloc(sizeof(node)-sizeof(node.c)+sizeof(node.c.b))
to reserve just enough space to hold fields x, y, z, and c.b[3].
It seems that the above expression might allocate more space than
necessary (due to packing considerations), but should work.  It
is also a kluge.  Is there a more elegant method?  Besides using
pascal?

-- 
Robert Herndon				Dept. of Computer Science,
...!ihnp4!umn-cs!herndon		Univ. of Minnesota,
herndon@umn-cs.ARPA			136 Lind Hall, 207 Church St. SE
herndon.umn-cs@csnet-relay.ARPA		Minneapolis, MN  55455

chris@mimsy.UUCP (Chris Torek) (06/09/87)

In article <1645@umn-cs.UUCP> herndon@umn-cs.UUCP (Robert Herndon) writes:
>... I want to represent nodes with a finite, fixed number of possible
>fields....  Some of these nodes will be relatively small, others large.
>I would like to allocate only enough memory for the union to hold the
>info necessary.  Is there any portable, COMPACT way to do this in C?

There is, but it relies upon an escape clause in K&R which may not
exist in the dpANS.  In the distant past (circa Research Version
6 Unix C) there were no unions; instead, one wrote

	/* example 1 */
	struct node {
		int	fixed_int;
		char	*fixed_str;
	};

	struct Anode {
		int	fixed_int;
		char	*fixed_str;
		struct	A a_part;
	};

	struct Bnode {
		int	fixed_int;
		char	*fixed_str;
		struct	B b_part;
	};

in place of

	struct node {
		int	fixed_int;
		char	*fixed_str;
		union {
			struct	A a_part;
			struct	B b_part;
		} u;
	};

In order for this to work, the compiler guarantees that in any two
structure declarations, if the `first part' of one structure matches
the `first part' of the second (here `fixed_int' and `fixed_str'),
those elements must lie at the same offsets and be addressed in
the same way in both structures.  This means that a `struct Anode'
and a `struct Bnode' can be treated as a `struct node' with impunity
(although in modern compilers it takes a type cast to change a
pointer from one kind to another).

Of course, the allocation

	struct Anode *p = (struct Anode *) malloc(sizeof (*p));

is perfectly legal; the assignment

	struct node *cathode = (struct node *) p;

is questionable, but (according to the escape clause) legal.  The
conversion back:

	struct Anode *Ap = (struct Anode *) cathode;

is then legal since `cathode' came from a cast from an Anode.

Assuming the compiler meets this requirement, and further (and
perhaps naively) assuming that it is required to begin all members
of a union at the same machine address, whatever `machine address'
`means', we can show that it must `do the right thing' with the
following:

	/* example 2 */
	struct node {
		int	fixed_int;
		char	*fixed_str;
		union {
			struct	A a;
			struct	B b;
		} u;
	};

	struct Anode {
		int	fixed_int;
		char	*fixed_str;
		struct	A a;
	};

	struct node *new_A_node()
	{
		struct node *p;

		p = (struct node *) malloc(sizeof (struct Anode));
		if (p == NULL)
			out_of_memory_abort();
		p->fixed_int = NODE_TYPE_A;
		p->fixed_str = NULL;
		p->u.a.i = 0;		/* whatever, etc. */
		return (p);
	}

The second assumption may not be correct; for certainty, see K&R or
Harbison & Steele, or if you are an optomist, the dpANS.

As to structure packing, if the second assumption does not hold,
proper massaging of the size of a `struct node' may save a few
bytes over example 2, but whatever arithmetic is required will be
entirely unportable.  Moreover, if this is the case, the original
`V6-style' declarations in example 1 will save the same amount of
memory, portably.  Three cheers for Version 6!
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7690)
Domain:	chris@mimsy.umd.edu	Path:	seismo!mimsy!chris

chris@mimsy.UUCP (Chris Torek) (06/09/87)

In article <6973@mimsy.UUCP> I wrote:
>... or if you are an optomist, [see] the dpANS.

An optometrist, perhaps?  Make that `optimist'.  It is late, and
I should be sleeping; I must get up early tomorrow to track down
those plane tickets so I can get to Usenix: B gave them to J; J
is on vacation ... a pretty problem.  (With luck, I will even have
time to work out before I must leave.  An unhackerly activity, I
know; but then, I believe in breaking images.  But what has any
of this to do with C?)
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7690)
Domain:	chris@mimsy.umd.edu	Path:	seismo!mimsy!chris

jfjr@mitre-bedford.arpa (06/09/87)

   If you want to allocate nodes oof various sizes efficiently
use an extra level of indirection


  struct{
    int constant_part
    int type_tag
    union{
      char * pointer_to_one_thing
      char * pointer_to_another_thing
      char * pointer_to_still_another}
    }

  I've used this a couple of times in Pascal and C - as long
as you are careful especially in C to keep your types straight
it works fine

                                           Jerry Freedman,Jr
                                           jfjr@mitre-bedford.arp3/