In article <11*********************@l77g2000hsb.googlegroups. com>
Serpent <fw**********@gmail.comwrote:
>The C-FAQ describes some techniques here:
http://c-faq.com/aryptr/dynmuldimary.html
I was using something slightly different from the C-FAQ and I was
wondering if it was legal.
Say I want a two-dimensional array, like this:
int x[2][3];
but I want it dynamically-allocated, and I want expressions that refer
to its elements to use the common subscript syntax.
int i = x[0][1];
I also want it to be one contiguous piece of memory, to avoid overhead
and complications.
Is this legal?
int (*x)[3] = (int (*)[3]) malloc(sizeof(int) * 2 * 3);
Yes, but it is better to write, e.g.:
int (*x)[3];
...
x = malloc(n * sizeof *x);
where "n" is the number of rows to use. If the malloc() succeeds,
you can then do:
for (i = 0; i < n; i++)
for (j = 0; j < 3; j++)
... operate on x[i][j] ...
Note that the number of columns is fixed: it must always be exactly
three. If you want the number of columns to be variable as well,
you have a problem.
In C99 (but not older versions of C), there is a new feature called
a "variable length array" or VLA. This new feature allows you to
change the number of columns, as well as the number of rows. Here,
you might write, e.g.:
void do_something(size_t m, size_t n) {
int arr[m][n];
size_t i, j;
for (i = 0; i < m; i++)
for (j = 0; j < n; j++)
... operate on arr[i][j] ...
/* "arr" is automatic, so it vanishes when we return */
}
If the array needs to have "allocated" storage duration, i.e., come
from malloc(), rather than having automatic storage duration, you
can still do that:
void do_more(size_t m, size_t n) {
int (*p)[n];
size_t i, j;
p = malloc(m * sizeof *p);
if (p == NULL)
... handle error ...
for (i = 0; i < m; i++)
for (j = 0; j < n; j++)
... operate on p[i][j] ...
}
but now there is an annoying problem: the type of "p" is "pointer
to VLA (of size n) of int", where n is determined by the call to
do_more(). So you must give up some degree of type-safety in order
to do something useful with the value in p (such as store it in a
return value, or store it in some "global" variable, or whatever)
before returning. It then becomes very easy to store the pointer
in a VLA of different "shape" (e.g., "pointer to VLA (of size 99)
of int" instad of "pointer to VLA (of size 33) of int"). If you
do this, tragedy will ensue. Hence, while VLAs are quite useful,
they are not a panacea.
If you do not have C99 (most people still do not, or at least not a
full version) and/or are unwilling to depend on having at least the
VLA feature, you have just two options.
A) Use a minimum of two malloc() calls: one to create the space
for the array, and one to create the space for a vector of
"row pointers". This technique is shown in the FAQ. It is
tempting to use one malloc() to create both regions, but to
do so invites the Gods of Alignment to strike your program
down when you least expect it. :-) (More seriously: some
machines have particular alignment constraints, and getting
both the row-vector *and* the data-area aligned requires
machine dependent trickery. The malloc() function contains
exactly this trickery, and calling it twice makes both areas
properly aligned.)
B) Use one malloc() call to allocate the data area, then do your
own "manual" calculations:
int *do_it_with_C89(size_t m, size_t n) {
int *p;
size_t i, j;
p = malloc(m * n * sizeof *p);
if (p == NULL)
... handle error ...
for (i = 0; i < m; i++)
for (j = 0; j < n; j++)
... operate on p[i * n + j] ...
return p;
}
Method (B) works in C99 as well, of course. While it is type-safe,
it is easy to make mistakes in remembering "m" and/or "n" and/or
doing the subscript calculation. You can minimize the chances of
such problems by using a structure to encapsulate everything:
/* in some header */
struct int_matrix {
size_t m;
size_t n;
int *p;
};
#ifndef NDEBUG
#define INT_MAT_ELEM(mp, i, j) \
(assert((i) < (mp)->m && (j) < (mp)->n), (mp)->p[(i) * (mp)->n + (j)])
#else
#define INT_MAT_ELEM(mp, i, j) ((mp)->p[(i) * (mp)->n + (j)])
#endif
/* in some source file that includes the above header, plus <stdlib.h*/
struct int_matrix *int_mat_create(size_t m, size_t n) {
struct int_matrix *mp;
size_t i, end;
mp = malloc(sizeof *mp);
if (mp == NULL)
return NULL;
mp->p = malloc(m * n * sizeof *mp->p);
if (mp->p == NULL) {
free(mp);
return NULL;
}
mp->m = m;
mp->n = n;
/* init to all-zeros */
for (i = 0, end = m * n; i < end; i++)
mp->p[i] = 0;
return mp;
}
void int_mat_destroy(struct int_matrix *mp) {
free(mp->p);
free(mp);
}
/* in some file that uses the integer matrix */
...
...
struct int_matrix *p;
p = int_mat_create(m, n);
if (p == NULL)
... handle error ...
...
INT_MAT_ELEM(p, i, j) = 42;
... other operations on INT_MAT_ELEM(p, i, j) ...
It is a little peculiar to have a function-like macro that expands
to an lvalue (so that you can assign to INT_MAT_ELEM(p,i,j) as in
the example above), but it should work.
--
In-Real-Life: Chris Torek, Wind River Systems
Salt Lake City, UT, USA (40°39.22'N, 111°50.29'W) +1 801 277 2603
email: forget about it
http://web.torek.net/torek/index.html
Reading email is like searching for food in the garbage, thanks to spammers.