#include #include #include /* This is a macro, ARRAY(), which creates space for a dynamically-bounded multi-dimensional array in C, for use in translating Imp's %array's to C. It creates the array off the stack using the non-portable alloca() call, and tweaks the lower bounds of each dimension so that arrays do not need to be based at 0. The complexity below is because one of the bounds parameters to an array declaration may be a function, and that function may itself declare a dynamically-bound array. Unlikely but possible. We need to use alloca because otherwise a real malloc would have to be explicitly freed at the end of the procedure, which is not only an amazing amount of extra compiler complexity (eg to handle %signals) but is also an unnecessary run-time overhead as malloc is generally rather expensive. We can't use gcc's extended dynamically-bound arrays because they're always 0-based. And you can't tweak the base by adjusting the pointer as in the code above. I would also prefer the access to array elements to look natural, eg x = arr[1][n][-3]; ... I don't want to have to make corrections to array indices at the point of usage, nor do I want to flatten the array into 1-D and simulate the dimensionality by scaling and adding the indices appropiately. Further complexity is caused by the possibility of the bounds of the array declaration being functions with side-effects, so care must be taken to evaluate them only once. push_evalonce takes the varargs and stores them in a stack, returning the base of the data. stacktop_evalonce returns the same data, leaving it on the stack pop_evalonce returns the same data, but then pops the stack. It uses a downward-growing stack so that the stacktop is always usable directly as the base of the lb/ub array, indexed by dim. Finally: data created by alloca() is lost on exit from the enclosing procedure, not the enclosing "{}" block, so we do have the option of wrapping the macro body in {} to take advantage of GCC's ability to use compound statements in macros, although we have not yet done so. The alloca() call must be made by the procedure which contains the declaration we're currently handling; it cannot be made in another procedure call. */ #define MAX_EVALONCE 1024 static int top_evalonce = MAX_EVALONCE; /* push then use, down-growing */ static int lb[MAX_EVALONCE]; /* lower bound */ static int ub[MAX_EVALONCE]; /* upper bound */ static int sz[MAX_EVALONCE]; /* size of object. ptr size or final objsize */ static int dm[MAX_EVALONCE]; /* number of dimensions that this array has */ static int imp_datasize_inner( int this, int *dm, int *sz, int *lb, int *ub) { return((ub[this]-lb[this]+1) * (sz[this] + (this == dm[this] ? 0 : (imp_datasize_inner(this+1, dm, sz, lb, ub))))); } static void *imp_arrayalloc_inner( int this, int spacep, int *dm, int *sz, int *lb, int *ub) { int i; int datap, p, ptr; if ((this == 0) && (dm[this] == 0)) { /* simple 1-D array */ return (void *)(spacep - (lb[this] * sz[this])); } p = spacep; /* between spacep and datap are pointers to next level */ datap = spacep + ((ub[this]-lb[this]+1) * sz[this]); for (i = lb[this]; i <= ub[this]; i++) { /* This code makes the assumption that sizeof(void *) <= sizeof(int) */ /* and that sizeof(x **) == sizeof(x *) */ if (this+1 == (dm[this])) { /* final bound, use contiguous array, don't recurse */ ptr = datap - (lb[this+1] * sz[this+1]); datap += (ub[this+1] - lb[this+1] + 1) * sz[this+1]; } else { ptr = (int)imp_arrayalloc_inner(this+1, datap, dm, sz, lb, ub); datap += imp_datasize_inner(this+1, dm, sz, lb, ub); } *(int *)p = ptr; p += sz[this]; } return (void *)(spacep - (lb[this] * sz[this])); } void *imp_arrayalloc(void *space, int decl_base) { return imp_arrayalloc_inner(0, (int)space, dm+decl_base, sz+decl_base, lb+decl_base, ub+decl_base); } int imp_datasize(int decl_base) { return imp_datasize_inner(0, dm+decl_base, sz+decl_base, lb+decl_base, ub+decl_base); } /* convert from stdargs to regular data for other procedures */ int push_evalonce(int dims, int objsize, int lb1, ...) { int i; va_list ap; int maxdim = dims - 1; top_evalonce -= dims; if (top_evalonce < 0) { fprintf(stderr, "dynamic array declaration: too many dynamically nested declarations\n"); fflush(stderr); assert(top_evalonce >= 0); } for (i = 0; i < maxdim; i++) { dm[top_evalonce+i] = maxdim; sz[top_evalonce+i] = sizeof(void *); } dm[top_evalonce+maxdim] = maxdim; sz[top_evalonce+maxdim] = objsize; va_start(ap, lb1); lb[top_evalonce] = lb1; ub[top_evalonce] = va_arg(ap, int); for (i = 1; i < dims; i++) { lb[top_evalonce+i] = va_arg(ap, int); ub[top_evalonce+i] = va_arg(ap, int); } va_end(ap); return top_evalonce; } int pop_evalonce(int dims) { int last = top_evalonce; top_evalonce += dims; return last; /* not interrupt-driven code so doesn't hurt to be outside */ /* anyway, there will be no more pushing calls until done. */ } /* extreme care needed here to evaluate stdarg params only once */ #define ARRAY(dims, type, lb, ub, ...) \ (push_evalonce(dims, sizeof(type), lb, ub, ##__VA_ARGS__), \ imp_arrayalloc(alloca(imp_datasize(top_evalonce)), pop_evalonce(dims)) \ ) int main(int argc, char **argv) { int i, j, k, q; int *p1 = alloca(0); long long int ***la = ARRAY(3, long long int, -1, 1, -1, 1, -1, 1); int *ap1 = ARRAY(1, long long int, -2, 6); int **ap2 = ARRAY(2, int, 0, 1, 0, 1); int ***ap3 = ARRAY(3, int, 0, 1, 0, 1, 0, 1); int ***ap = ARRAY(3, int, 0, 5, 0, 16, 0, 20); long long int ***lap3 = ARRAY(3, long long int, -1, 1, -2, 2, -3, 3); int *p2 = alloca(0); int *p3; p3 = alloca(4); fprintf(stderr, "p1 = %08x, p2 = %08x, p3 = %08x, la = %08x\n", p1, p2, p3, la); q = 42; for (k = -1; k <= 1; k++) { for (j = -1; j <= 1; j++) { for (i = -1; i <= 1; i++) { fprintf(stdout, "&(la[%d][%d][%d]) = %p, ", i,j,k, &(la[i][j][k])); la[i][j][k] = q++; } fprintf(stdout, "\n"); } fprintf(stdout, "\n"); } q = 42; for (k = -1; k <= 1; k++) { for (j = -1; j <= 1; j++) { for (i = -1; i <= 1; i++) { if (la[i][j][k] != q++) { fprintf(stderr, "error: cannot read back value. item corrupt.\n"); } } } } return(0); }