[net.sources] Retraction

rfb@h.cs.cmu.edu (Rick Busdiecker) (11/18/85)

In a previous post I said that the declarations:

	double matrix [m][n]

and

	double (matrix [m])[n]

were not equivalent.  Duane Williams pointed out and cc convinced me that I
was wrong.  In order to treat a two dimensional array as **matrix you need
to set the pointers up yourself.  The routine make_mat() in the following
program works (I tested it this time!).  This type of matrix is slightly
more versatile than the one you get with a standard declaration because it
contains the standard representation which you can refer to as (test+m) when
passing it to a function which takes standard matrices.



#define HEIGHT	2
#define WIDTH	3

char *valloc ();

double **make_mat (m, n)
{
    double	**result;
    int		i;

    result = (double **)valloc (m * (n + 1));
    for (i = 0; i < m; i++)
        *(result + i) = (double *)result + m + i * n;
    return (result);
}


main ()
{
    double	**test;
    int		i, j;

    test = make_mat (HEIGHT, WIDTH);
    for (i = 0; i < HEIGHT; i++)
	for (j = 0; j < WIDTH; j++)
	{
	    test [i][j] = i * WIDTH + j;
	    printf ("test [%d][%d] = %g\t%d\n", i, j, test [i][j],
		    (&(test [i][j]) - &(test [0][0])));
	}
    putchar ('\n');
    for (i = 0; i < HEIGHT; i++)
	for (j = 0; j < WIDTH; j++)
    	    printf ("test [%d][%d] = %g\n", i, j, test [i][j]);
}



Sorry for any confusion I caused people.

---------------------------------------------------------------------------
 Rick Busdiecker                       ARPA:    rfb@h.cs.cmu.edu           
 Carnegie-Mellon University            UUCP:    ...!seismo!h.cs.cmu.edu!rfb 
 Mathematics Department                AT&T:    (412) 521-1459             
                                       USPS:    4145 Murray Ave. 15217     
---------------------------------------------------------------------------

hester@ICSE.UCI.EDU (Jim Hester) (11/19/85)

The make_mat procedure given assumes that double pointers are the same
size as doubles, and further assumes doubles are the data type.  The second
assumption may not matter in a given program, but the first might easily
trash the program if ported to a system where pointers are not equivelent
(size-wise) to doubles.  The following corrects this, and is consistant
in usage with malloc and calloc.  From your example I infer that valloc
is no different.

	char *malloc();
 
	char **make_mat (rows, cols, elt_size)
	int rows, cols, elt_size;
	{
		char	**result;
		int	i;

		result = (char **) malloc (rows * sizeof(char *));
		result[0] = (char *) malloc (rows * cols * sizeof(char *));
		for (i = 1; i < rows; i++)
		    result[i] = result[i-1] + cols*elt_size;
		return (result);
	}

	void free_mat (matrix)
	char **matrix;
	{
		free( matrix[0] );
		free( matrix );
	{

The call for an N by M matrix of doubles would then be:

	double **matrix;
	matrix = (double **) make_mat( N, M, sizeof(double) );
and to free:
	free_mat( (char **) matrix );


The previous code still assumes that all pointers (ie char and double
pointers) are of the same size.  I generally allow this assumption
since I like the calling syntax and tend to think that pointers WILL
be the same size (ie byte addresses to the head of whatever type),
but if you want to avoid this potential problem you may resort to
macros at a slight degridation (in my opinion) of calling syntax:

	char *malloc();
 
	#define make_mat (matrix, rows, cols, type)\
	{\
		int	i;\
		int	rws = rows;	/* to precalculate expressions */\
		int	cls = cols;\
\
		matrix = (type **) malloc (rws * sizeof(type *));\
		matrix[0] = (type *) malloc (rws * cls * sizeof(type));\
		for (i = 1; i < rws; i++)\
		    matrix[i] = matrix[i-1] + cls;\
	}

	#define free_mat(matrix)\
	{\
		free( (char *) matrix[0];\
		free( (char *) matrix;\
	}

The call should now be:
	double **matrix;
	make_mat(matrix, N, M, double);
and to free:
	free_mat(matrix);
	

WARNING:  I have not tested any of this code.  I believe it to be sound
in theory, but may need debugging.  I kind of doubt it though---it's
fairly straightforward stuff that I've used before.

hester@ICSE.UCI.EDU (Jim Hester) (11/20/85)

Re your code to make a pointer-vector matrix:

 > char *valloc ();
 > 
 > double **make_mat (m, n)
 > {
 >     double	**result;
 >     int		i;
 > 
 >     result = (double **)valloc (m * (n + 1));
 >     for (i = 0; i < m; i++)
 >         *(result + i) = (double *)result + m + i * n;
 >     return (result);
 > }

and your reply to my comments on that code:

 > Thanks for pointing out my error.  Pointers are not the same size as
 > doubles on my system (pointers 4 bytes, doubles 8) so I'm not sure why
 > it  worked for me.  I'll check it sometime and see what was going on.

Curious myself to know why it worked, I looked up the definition
of valloc (I've never used it), and it partially answers the problem.
The rest of the reason comes from a bit of luck.  Valloc returns space
beginning on a page boundry by calling malloc with a bloated size to
force a new page to be allocated, so you get at least a page of memory
actually allocated.  You only asked for n bytes for pointers (1/4 of
what you need) and m*n bytes for double storage (1/8 of what you need)
but, for small tests, the extra space on a page would be enough to
avoid errors.

In allocating the rows, result is massaged to type (double *), which
makes the associated arithmetic specify enough space for the true
size of doubles.  Thus, for the vector of pointers, you allocate
m elements the size of doubles: twice what you need for the pointers.
Although the vector of pointers is larger than needed, no error occurs
since the extra space is just never accessed when your other programs
later access this space as pointers and the wasted space is also
absorbed by the full page allocation.

I believe that, in a moderately large case, the factors of 4 and 8
would catch up with you and you would get errors due to referencing
memory outside of that which was allocated.

Interesting case.  Thanks for the puzzle, and giving me a reason to
look up valloc.  I'd never heard of it before.

	Jim