Problem with va_ macros and arrays of arrays

R

rir3760

Since a few days ago I have been working with the program I post
below (a school assignment). The purpose of the program is to work
with the va_ macros (stdarg.h) and arrays of arrays, hopefully
learning a little bit in the process.

The objective of the fn_memset function is to set all the chars in
the arrays passed (of type array N of array M of char) to a
specific char, somewhat similar to what the standard function
memset does:

#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>

#define N_MAT 10
#define T_NOM 40
#define T_NIF 10
#define T_POB 15

char alu_nom[N_MAT][T_NOM];
char alu_nif[N_MAT][T_NIF];
char alu_pob[N_MAT][T_POB];

void fn_memset(char car, int n_args, ...);

int main(void)
{
int i;

fn_memset('A', 6, T_NOM, alu_nom, T_NIF, alu_nif,
T_POB, alu_pob);

for (i = 0; i < N_MAT; i++){
alu_nom[T_NOM - 1] = '\0';
alu_nif[T_NIF - 1] = '\0';
alu_pob[T_POB - 1] = '\0';
}

puts("Array alu_nom[][]:");
for (i = 0; i < N_MAT; i++)
printf("%s\n", alu_nom);
printf("\n");

puts("Array alu_nif[][]:");
for (i = 0; i < N_MAT; i++)
printf("%s\n", alu_nif);
printf("\n");

puts("Array alu_pob[][]:");
for (i = 0; i < N_MAT; i++)
printf("%s\n", alu_pob);
printf("\n");

return EXIT_SUCCESS;
}

void fn_memset(char car, int n_args, ...)
{
int i, j, tam;
char *aux;
va_list pa;

va_start(pa, n_args);
while(n_args > 0){

tam = va_arg(pa, int);
aux = va_arg(pa, char *);

for (i = 0; i < N_MAT; i++)
for (j = 0; j < tam; j++)
*(aux + i * tam + j) = car;

n_args -= 2;
}
va_end(pa);
}

The compiler (and splint) didn't emit any errors, and the program
produced the expected output with no run-time errors but I think I
just got 'lucky behaviour'.

This because I discovered (with horror ;-) that the operation of
'flattening an array' is illegal as the pointer to char moves from
one array M of char to another one, or at least that's what I
found in the book 'Pointers on C' by Kenneth A. Reek.

As I'm still a beginner in C without a copy of the standard I
don't want to fall into the scenario "OK let's try such and such
and if it works in my system then ...". Instead what better than
asking the pros of comp.lang.c a few questions? ;-)

First, can the function fn_memset be implemented with standard C?

Second, can a cast consist of values that are calculated at run-
time? For example:

void fn_memset(void *a, size_t n, size_t m)
{
size_t i, j;

for (i = 0; i < n; i++)
for (j = 0; j < m; j++)
((char (*)[m]) a)[j] = 'x';
}

Third (and related), can a pointer declaration consist of values
that are calculated at run-time? In a similar vein:

void fn_memset(void *a, size_t n, size_t m)
{
size_t i, j;
char (*foo)[m] = a;

for (i = 0; i < n; i++)
for (j = 0; j < m; j++)
foo[j] = 'x';
}

Last question, as you can see I have many holes in my knowledge of
C so please can someone point me to didactic material (preferably
online) about the correct use of declarations, casting and
typedef's?

Thanks for your time.
 
D

Dave Thompson

Since a few days ago I have been working with the program I post
below (a school assignment). The purpose of the program is to work
with the va_ macros (stdarg.h) and arrays of arrays, hopefully
learning a little bit in the process.

The objective of the fn_memset function is to set all the chars in
the arrays passed (of type array N of array M of char) to a
specific char, somewhat similar to what the standard function
memset does:
char alu_nom[N_MAT][T_NOM];
char alu_nif[N_MAT][T_NIF];
char alu_pob[N_MAT][T_POB];
Aside: it appears here you want to have several (parallel) pieces of
data or attributes about a series of entities. You can also achieve
that with an array of struct, or possibly a pointer to a dynamic array
of struct, and usually that's easier to code and maintain. For
learning of course you should learn both (and in general all options),
and if your teacher has decided to go this way first, or at this time,
I realize that's not your responsibility.

fn_memset('A', 6, T_NOM, alu_nom, T_NIF, alu_nif,
T_POB, alu_pob);
void fn_memset(char car, int n_args, ...)
{
int i, j, tam;
char *aux;
va_list pa;

va_start(pa, n_args);
while(n_args > 0){

tam = va_arg(pa, int);
aux = va_arg(pa, char *);

for (i = 0; i < N_MAT; i++)
for (j = 0; j < tam; j++)
*(aux + i * tam + j) = car;
I hope you know that's equivalent to aux[i*tam+j] = car;
which is more usual and thus easier to read.
n_args -= 2;
}
va_end(pa);
}

The compiler (and splint) didn't emit any errors, and the program
produced the expected output with no run-time errors but I think I
just got 'lucky behaviour'.

This because I discovered (with horror ;-) that the operation of
'flattening an array' is illegal as the pointer to char moves from
one array M of char to another one, or at least that's what I
found in the book 'Pointers on C' by Kenneth A. Reek.
For a non-char array say int x[3][6] using x[0][12] to access x[2][0]
is technically illegal, but in practice it always works. Moreover,
when accessing any object (area of memory) using a pointer to
specifically character type (plain char, signed char, or unsigned
char), you are guaranteed to be able to address all the bytes, and
store to them (as you do); in C99 you are guaranteed to fetch them
using pointer to unsigned char, which cannot have padding bits or trap
representations, but maybe not signed char, and thus not plain char if
it is signed, although in practice those work too. In C89 this was
worded more loosely and it may be guaranteed that pointer to signed
char also works; it certainly does in practice.

You are actually doing something different: you are passing a
char(*)[N] as a vararg thus without any conversion and picking it up
as char*. This is not guaranteed to work; different kinds of pointers,
that is to different types, can have different representations (and
even sizes). In practice I don't know of and can't imagine an
implementation where a pointer to char array would have any reason to
be different from a pointer to char, but in theory it could happen. (I
_do_ know rare cases where int *, or int(*)[N], or struct foo * or
struct foo (*)[N], differs from char * and char (*)[N] and void *.)

If you cast the pointer _in the call_ it would thus be totally safe:
fn_memset ('A', 2, T_NOM, (char*)alu_nom);

Alternatively, pointer to array-unbounded is required to be compatible
with pointer to array of any fixed size, so you could pick them up as:
typedef chararyptr char (*)[];
chararyptr auxa = va_arg (pa, chararyptr);
char * aux = *auxa /* or & * (*aux) to be explicit */
/* or just use (*auxa)[i*m+j] or similar */
(The typedef is needed because the typename in va_arg must be validly
'pointerized' by just appending an asterisk.)

That said, for real code you should just use memset(); that's what
it's there for, it's (more) easily understood by anyone reading your
code, it's one less thing to write and maintain and manage, and it's
probably better optimized especially across platforms.
As I'm still a beginner in C without a copy of the standard I
don't want to fall into the scenario "OK let's try such and such
and if it works in my system then ...". Instead what better than
asking the pros of comp.lang.c a few questions? ;-)
Very good thinking. "What my implementation does" is not the right
criteria _especially_ for C which deliberately leaves quite a bit of
leeway -- some people feel too much -- for implementation variations.
The standard isn't written as a tutorial and is difficult to read
unless you already understand the concepts pretty well -- which it
appears to me you do -- but if you want it you can purchase it in PDF
form for individual use for USD 18 from ANSI, the US national body, at
webstore.ansi.org, or as a deadtree book from Wiley with BSI the UK
national body ISBN 0470845732 I believe about USD 40; or you can get
the last publicly circulated draft in PDF, txt, and IIRC ps from the
committee website std.dkuug.dk/JTC1/SC22/WG14/documents/n869/ .
The draft is not exactly the standard, there were some small changes,
but it is close enough to do a good deal of learning from. But, if you
use it and need to post a citation here regarding some question, say
it's from n869, so if you happen upon one of the few points that is
different we will understand and not think you incompetent.

There have actually been two "TCs" (Technical Corrigenda) to C99,
which are both available free (last I looked) from ANSI, and I've been
told the Wiley book incorporates TC1. Again these changes are minor.
First, can the function fn_memset be implemented with standard C?
See above.
Second, can a cast consist of values that are calculated at run-
time? For example:
((char (*)[m]) a)[j] = 'x';
}

Third (and related), can a pointer declaration consist of values
that are calculated at run-time? In a similar vein:

char (*foo)[m] = a;

The bound(s) of a nonstatic nonmember array type (only) can be runtime
values, including those two examples, in standard C99 (not yet widely
implemented) or in GNU-C89 aka GCC as an extension (very widely
available). Some other cases that would match your question as worded,
like an array bound in a struct, a bitfield in a struct, or even a
(stupidly placed) enum, cannot.
Last question, as you can see I have many holes in my knowledge of
C so please can someone point me to didactic material (preferably
online) about the correct use of declarations, casting and
typedef's?
I'd say you're doing pretty well to be asking questions at this level,
and as far as my opinion matters (which isn't very far) you're welcome
to continue. There's always the FAQ, at the usual places and a
slightly old version at http://www.eskimo.com/~scs/C-faq/top.html .
Beyond that I don't have any good, er, pointers.

- David.Thompson1 at worldnet.att.net
 

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

No members online now.

Forum statistics

Threads
473,754
Messages
2,569,528
Members
45,000
Latest member
MurrayKeync

Latest Threads

Top