Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

Légallité d'un pointeur de tableaux

44 réponses
Avatar
Stéphane Zuckerman
Bonjour,

Je m'interroge sur une construction peu orthodoxe que je dois utiliser
en C (=E0 savoir un pointeur de tableaux).

J'ai une fonction dont le prototype est =E0 peu pr=E8s le suivant :

void f(float (*tab)[N], /* reste des arguments */);

Jusque l=E0, tout va bien. Dans le main(), j'alloue "tab" =E0 l'aide des
lignes suivantes :

#define M ...
#define N ...

int main(void)
{
/* d=E9clarations ... */
float (*t)[N];

t =3D malloc(sizeof(float) * M * N);
if (t =3D=3D NULL) { /* gestion d'erreur */ }

/* ... */
f(t, ...);

return 0;
}

J'ai cherch=E9 un peu partout, demand=E9 =E0 des personnes qui =E0 ma
connaissance connaissent bien la norme, et ce code semble l=E9gal.
J'aimerais d'une part avoir confirmation, et d'autre part savoir si
quelqu'un pourrait me dire o=F9 dans la norme je peux confirmer cela.

Merci d'avance ! :-)

--
St=E9phane Zuckerman

10 réponses

1 2 3 4 5
Avatar
candide
Antoine Leca a écrit :
Le 02/06/2009 18:03, candide écrivit :
Certes mais la directive

#define M



... peut être modifiée en

int m;
#define M m

en un instant,



Tu introduis une variable, ce n'est plus du tout le même code.


du code initial suggère qu'on n'alloue pas à l'exécution.



Ah bon ? tu trouves qu'il est plus facile d'aller chercher
... = malloc(2500 * 345 * sizeof(truc) )
et de changer le 2500 par 3500, parce que les résultats du code
Navier-Stockes ne sont pas probants, ou parce que on peut se permettre
de passer plus de temps dans les calculs matriciels ?

Et de recommencer à l'envers lorsque les résultats ne s'améliorent pas...




Comprends pas ta réaction, j'ai rien contre les macro-constantes, bien au
contraire. Dans le code du PO, tout suggère que les choix de taille se font à la
compilation (typique d'un #define) et non à l'exécution, et mise à part le
problème d'allocation sur la pile, il est assez naturel de songer à des tableaux
statiques comme suggéré par LL.

Pour élargir, je pense qu'on doit pouvoir enseigner le C presque seulement avec
des exemples mais c'est un art que de savoir construire les exemples. Pas
d'affolement, cette remarque ne s'adresse ni à toi ni au PO d'ailleurs.
Avatar
Jean-Claude Arbaut
candide wrote:
Jean-Claude Arbaut a écrit :

Pour éviter de coder les dimensions en dur, ne peut-on pas faire
la chose suivante ? (en tout cas "gcc -ansi -Wall" ne crie pas)

double get(int n, int p, double t[n][p], int i, int j) {
return t[i][j];
}




:~$ gcc -W -Wall -stdÈ9 -pedantic -c fclc.c
fclc.c:3: attention : ISO C90 forbids variable-size array «t»
fclc.c:3: attention : ISO C90 forbids variable-size array «t»



C'est dans le nouveau standard: -stdÉ9 et ça passe ;-)

L'allocation peut se faire avec
double (*t)[];



Tiens, j'imaginais pas qu'on pouvait déclarer un pointeur vers un tableau incomplet.



On peut garder la dernière dimension incomplète. C'est pour ça
que je galère avec le tableau à trois dimensions: il y en a une
de trop, il veut pour celle-là une taille constante. Je suppose
que le coup a été prévu par le C99: si on peut déclarer des
fonctions à tableaux variables, il faut bien pouvoir
déclarer les arguments qu'on leur passe... j'espère :o)

t = (double (*)[])calloc(n*p, sizeof(double));




Tu castes le retour de calloc() ?



Bonne remarque :-) Je crois que je suis tombé un jour sur un compilateur
qui fait des warnings sur les malloc/calloc non castés, du coup c'est
resté. Je ne me rappelle pas les circonstances exactes (peut-être un
gcc 2.x sous macosx, mais je le jurerais pas).

Tiens, pour me simplifier la vie j'utilisais ça :

#define new(n,t) ((t *)calloc(n, sizeof(t)))
Avatar
Alexandre Bacquart
Jean-Claude Arbaut wrote:

Tiens, pour me simplifier la vie j'utilisais ça :

#define new(n,t) ((t *)calloc(n, sizeof(t)))



Comme quoi, il y a des claques qui se perdent !


--
Alex
Avatar
Jean-Claude Arbaut
Alexandre Bacquart wrote:
Jean-Claude Arbaut wrote:

Tiens, pour me simplifier la vie j'utilisais ça :

#define new(n,t) ((t *)calloc(n, sizeof(t)))



Comme quoi, il y a des claques qui se perdent !





Ce n'est pas illégal :o)
Avatar
Lucas Levrel
Le 3 juin 2009, candide a écrit :

Comprends pas ta réaction, j'ai rien contre les macro-constantes, bien au
contraire. Dans le code du PO, tout suggère que les choix de taille se font à la
compilation (typique d'un #define) et non à l'exécution,



Oui, c'est pour ça que je demandais pourquoi pas float t[M][N]. Dans le
cas général je connais les avantages de l'allocation dynamique, mais là ça
n'avait pas l'air d'être le cas.

et mise à part le problème d'allocation sur la pile,



Là j'ai appris un truc ! Je ne savais pas du tout (ou j'avais oublié) que
ma suggestion et malloc n'utilisaient pas la même partie de la mémoire.
(Sans doute un inconvénient du C par l'exemple.) Qu'est-ce qui détermine
l'espace libre pour la pile ?

--
LL
Avatar
Jean-Claude Arbaut
Lucas Levrel wrote:

Le 3 juin 2009, candide a écrit :

Comprends pas ta réaction, j'ai rien contre les macro-constantes, bien au
contraire. Dans le code du PO, tout suggère que les choix de taille se font à la
compilation (typique d'un #define) et non à l'exécution,



Oui, c'est pour ça que je demandais pourquoi pas float t[M][N]. Dans le
cas général je connais les avantages de l'allocation dynamique, mais là ça
n'avait pas l'air d'être le cas.

et mise à part le problème d'allocation sur la pile,



Là j'ai appris un truc ! Je ne savais pas du tout (ou j'avais oublié) que
ma suggestion et malloc n'utilisaient pas la même partie de la mémoire.



Il suffit d'allouer en dehors de la fonction pour ne plus
avoir de problème. La variable est alors en dur dans l'exécutable,
dans le .bss en principe (ou un équivalent). Pour des gros
tableaux, ça ne va pas changer grand chose, puisque malloc
ferait de toute façon un mmap (sous linux au moins).

(Sans doute un inconvénient du C par l'exemple.) Qu'est-ce qui détermine
l'espace libre pour la pile ?



Il me semble que c'est codé dans l'exécutable (modifiable par le lieur
?), et que l'OS a aussi une limite.
Avatar
Lucas Levrel
Le 3 juin 2009, Jean-Claude Arbaut a écrit :

> > L'allocation peut se faire avec
> > double (*t)[];

On peut garder la dernière dimension incomplète. C'est pour ça
que je galère avec le tableau à trois dimensions: il y en a une
de trop, il veut pour celle-là une taille constante. Je suppose
que le coup a été prévu par le C99: si on peut déclarer des
fonctions à tableaux variables, il faut bien pouvoir
déclarer les arguments qu'on leur passe... j'espère :o)



Pour faire strictement ce que tu sembles vouloir, je vois deux
possibilités à priori :
- double *t[][] : ne marche pas car comment faire de l'arithmétique sur le
pointeur en ne connaissant pas la taille de la dernière dimension ?
- double **t[] : marche mais c'est à toi de construire les *t[][x].

Comme je ne suis probablement pas très clair ;-), voilà ce que je fais
pour 4 dimensions par exemple (c'est du C++ mais probablement pas dur à
traduire en C) :
float ****table;
table=new float***[a];
for(int ia=0;ia<a;ia++){
table[ia]=new float**[b];
for(int ib=0;ib<b;ib++){
table[ia][ib]=new float*[c];
for(int ic=0;ic<c;ic++){
table[ia][ib][ic]=new float[d];
}
}
}

Bien sûr là les éléments ne sont pas forcément contigus en mémoire. On
doit pouvoir aussi faire malloc(sizeof(float)*a*b*c*d),
malloc(sizeof(float*)*a*b*c), etc., puis définir les pointeurs vers les
éléments adéquats.

--
LL
Avatar
Jean-Claude Arbaut
Lucas Levrel wrote:
Le 3 juin 2009, Jean-Claude Arbaut a écrit :

L'allocation peut se faire avec
double (*t)[];




On peut garder la dernière dimension incomplète. C'est pour ça
que je galère avec le tableau à trois dimensions: il y en a une
de trop, il veut pour celle-là une taille constante. Je suppose
que le coup a été prévu par le C99: si on peut déclarer des
fonctions à tableaux variables, il faut bien pouvoir
déclarer les arguments qu'on leur passe... j'espère :o)






Pour faire strictement ce que tu sembles vouloir, je vois deux
possibilités à priori :
- double *t[][] : ne marche pas car comment faire de l'arithmétique sur le
pointeur en ne connaissant pas la taille de la dernière dimension ?
- double **t[] : marche mais c'est à toi de construire les *t[][x].

Comme je ne suis probablement pas très clair ;-), voilà ce que je fais
pour 4 dimensions par exemple (c'est du C++ mais probablement pas dur à
traduire en C) :
float ****table;
table=new float***[a];
for(int ia=0;ia<a;ia++){
table[ia]=new float**[b];
for(int ib=0;ib<b;ib++){
table[ia][ib]=new float*[c];
for(int ic=0;ic<c;ic++){
table[ia][ib][ic]=new float[d];
}
}
}

Bien sûr là les éléments ne sont pas forcément contigus en mémoire. On
doit pouvoir aussi faire malloc(sizeof(float)*a*b*c*d),
malloc(sizeof(float*)*a*b*c), etc., puis définir les pointeurs vers les
éléments adéquats.



Oui oui, je connais des alternatives ;-) Ce qui me semblait bizarre,
c'est qu'on peut déclarer

double get(int n, int p, int q, double t[n][p][q], int i, int j, int k)
{
return t[i][j][k];
}

Mais on ne peut pas déclarer un pointeur sur une telle variable
tableau, qui serait alloué par malloc/calloc.
Je peut très bien faire

double *t = malloc(n*p*q*sizeof(double);
a=get(n,p,q,t,0,0,0);

Ca fera un warning mais ça passe.
Par contre, je ne vois pas comment le déclarer sans warning.

Il n'y a pourtant aucun calcul mal défini là-dedans il
me semble. En fait j'aimerais pouvoir faire ce qu'on fait
dans les prototypes:

double get(int n, int p, int q, double t[*][*][*], int i, int j, int k);

et déclarer double (*t)[*][*];
ou un truc équivalent.
Mais le compilateur refuse évidemment.

Ca ne serait pas un problème majeur qu'il ne puisse
pas calculer les indices sans connaître les dimensions,
dans la fonction appelante puisqu'il lui suffirait de dire:
c'est un tableau déclaré sans dimension connue, je ne sais
pas quoi faire avec, point. Dans la fonction appelée
il saurait.
On pourrait aussi imaginer faire un cast dans la fonction
appelante vers un double (*)[x][y] pour pouvoir indicer.

C'est exactement ce qu'on fait quand on passe de void* à qqch*:
avec void *, le compilateur ne peut pas déréférencer,
mais il est tout de même capable d'allouer, quitte à
caster ensuite vers qqch* pour indicer.

Peut-être que j'en demande beaucoup :o)
Avatar
candide
Jean-Claude Arbaut a écrit :
candide wrote:
Jean-Claude Arbaut a écrit :

Pour éviter de coder les dimensions en dur, ne peut-on pas faire
la chose suivante ? (en tout cas "gcc -ansi -Wall" ne crie pas)

double get(int n, int p, double t[n][p], int i, int j) {
return t[i][j];
}




:~$ gcc -W -Wall -stdÈ9 -pedantic -c fclc.c
fclc.c:3: attention : ISO C90 forbids variable-size array «t»
fclc.c:3: attention : ISO C90 forbids variable-size array «t»



C'est dans le nouveau standard: -stdÉ9 et ça passe ;-)



Oui, je sais mais je croyais que gcc -ansi (cf. ce que tu écris ci-dessus)
référait au standard ansi de 1989 (ie C iso 90) ce qui n'est finalement pas le
cas. La logique des options de gcc resteront toujours un mystère pour moi.




L'allocation peut se faire avec
double (*t)[];



Tiens, j'imaginais pas qu'on pouvait déclarer un pointeur vers un
tableau incomplet.



On peut garder la dernière dimension incomplète



En effet, il s'avère qu'il est bien légal de déclarer des pointeurs vers des
tableaux incomplets (en fait vers des types incomplets, au hasard void) :

§6.2.5
A pointer type may be derived from a function type, an object
type, or an incomplete type, called the referenced type.


mais je ne vois pas l'intérêt de ce genre de pointeur, en particulier on ne
dispose pas d'arithmétique des pointeurs.
Avatar
Jean-Claude Arbaut
candide wrote:

Oui, je sais mais je croyais que gcc -ansi (cf. ce que tu écris ci-dessus)
référait au standard ansi de 1989 (ie C iso 90) ce qui n'est finalement pas le
cas. La logique des options de gcc resteront toujours un mystère pour moi.



Ah, oui, j'avais zappé ça. En prime ça a dû changer suivant les
versions de gcc...


En effet, il s'avère qu'il est bien légal de déclarer des pointeurs vers des
tableaux incomplets (en fait vers des types incomplets, au hasard void) :

§6.2.5
A pointer type may be derived from a function type, an object
type, or an incomplete type, called the referenced type.


mais je ne vois pas l'intérêt de ce genre de pointeur, en particulier on ne
dispose pas d'arithmétique des pointeurs.



On n'en dispose pas non plus avec un void*, pourtant un void* ça sert
beaucoup ;-)

Cela dit, je suis sot: en pratique, pour pouvoir allouer, il
faut bien connaître les dimensions, et il suffit donc faire


double f(int n, double a[*][*][*]);

double g() {
int n;
double (*t)[n][n];
t = malloc(8*n*n*n);
return f(n, t);
}



J'étais fixé sur l'idée de déclarer un tableau
sans dimension pour l'allouer :o)


Par contre, on peut aussi faire mumuse:

double f(int n, int p, int q, double a[n][p][q]);

double g() {
int n, p , q0;
double a, b;
double (*t)[p][q];
t = malloc(8*n*p*q);
a = f(n,p,q,t);
b = f(q,n,p,t);
return a+b;
}

Et le tableau qu'on déclare change de dimension
comme on veut, la déclaration d'origine
n'a aucune importance. Ce serait quand
même plus propre de le déclarer (*)[*][*] ;-)
1 2 3 4 5