exhortae a écrit :
Voilà je veux faire une fonction qui lit les élements d'une matrice mais le code suivant refuse de se compiler :
Code :
- #include <stdio.h>
- void lire_tab (int **m, int l, int c)
- {
- int i, j;
- for (i = 0; i < l; i++)
- for (j = 0; j < c; j++)
- {
- printf ("M[%d,%d] = ", i + 1, j + 1);
- scanf ("%d", &m[i][j]);
- }
- }
|
|
Bienvenue dans le monde des tableaux à plusieurs dimensions !!!
Le pb du débutant, c'est qu'il a tendance à croire que [] <=> * donc [][] <=> **
L'équivalence n'est vrai que pour une seule dimension. Pourquoi ? parce que la mémoire n'est définie, en mémoire, que sur une seule dimension. Les cases mémoires sont alignées sur une seule et immense ligne.
Donc le code suivant
void init(int tab[], int max)
{
int i;
for (i=0; i < max; i++)
tab[i]=0; // Ou *(tab + i)=0
} |
Peut être sans problème remplacé par
void init(int *tab, int max)
{
int i;
for (i=0; i < max; i++)
tab[i]=0; // Ou *(tab + i)=0
} |
Avec le main unique
int main()
{
int tablo[10];
init(tablo, 10);
} |
Avec 2 dim, ce n'est plus du tout vrai. Parce que, pour un tableau de 5 lignes sur 10 colonnes, style "int t[5][10]", lorsque tu fais "t[i][j]", le compilo remplace par "la case située à i * 10 + j". Il en ressort que si tu passes "t" à une fonction, il faut que la fonction connaisse quand-même la largeur du tableau pour pouvoir calculer la position de ses cases.
Donc le code suivant
void init(int tab[5][10], int lig, col)
{
int i,j;
for (i=0; i < lig; i++)
{
for (j=0; j < col; j++)
tab[i][j]=0; // Ou *(*(tab + i)) + j=0
}
} |
Peut être sans problème remplacé par
void init(int *tab[10], int lig, col)
{
int i,j;
for (i=0; i < lig; i++)
{
for (j=0; j < col; j++)
tab[i][j]=0; // Ou *(*(tab + i)) + j=0
}
} |
Mais ne peut absolument pas être remplacé par
void init(int **tab, int lig, col)
{
int i,j;
for (i=0; i < lig; i++)
{
for (j=0; j < col; j++)
tab[i][j]=0; // Ou *(*(tab + i)) + j=0
}
} |
Car là, dans la fonction "init", le compilo ne connait pas la largeur du tableau et ne peut donc pas calculer la position de l'élément demandé.
Le main, lui, reste unique
int main()
{
int tablo[5][10];
init(tablo, 5, 10);
} |
En fait, l'équivalence [] <=> * ne marche que pour la première dimension du tableau. Et dès qu'on travaille en plusieurs dim, t'es obligé de conserver les autres.
exhortae a écrit :
en essayant de le corriger, j'arrive à ça :
Code :
- int main (void)
- {
- int *a[10], l, c, i, j;
- printf ("Nombre de lignes : " );
- scanf ("%d", &l);
- printf ("Nombre de colonnes : " );
- scanf ("%d", &c);
- lire_tab (a, l, c);
- for (i = 0; i < l; i++)
- {
- for (j = 0; j < c; j++)
- printf ("\t%d", a[i][j]);
- printf ("\n" );
- }
- return 0;
- }
|
ça à le mérite de fonctionner
|
C'est le pire danger qui pouvait t'arriver: En ligne 3 tu déclares "a" un tableau de 10 pointeurs. Puis, sur ces pointeurs qui pointent on ne sais-où, tu vas y remplir des trucs. Mais tu ne sais absolument pas si tu ne pointes pas sur des zones allouées à d'autres variables (il n'y a qu'une mémoire). Le résultat te donne un comportement indéfini, c'est à dire où tout peut arriver (j'aime bien cette définition donnée par Emmanuel Delahaye), y compris que cela fonctionne parce que, par chance, tu tapes dans des zones libres. Puis, un jour, tu rajoutes un peu de code banal, style un autre tableau, un printf(), une fonction, un truc totalmeent bateau et là ça plante. Et évidement tu chercheras la cause du plantage dans les quelques lignes que t'auras rajouté alors qu'elle sera en ligne 3.
Pour éviter ce danger, il faut toujours se rappeler d'un truc très simple: quand tu as un pointeur style "int *t", tu n'as pas le droit d'aller taper dans "t[i]" (qui est aussi "*(t + i)" ) si t'as pas écrit avant "t=<qqchose>". Ca peut être n'importe quoi mais faut que ce soit valide. Evidemment les deux seuls trucs qu'on peut mettre dans "<qqchose>" sont
- une adresse valide (par exemple celle d'un tableau déjà alloué)
- une réservation mémoire (malloc)
Exemples
int tablo[10];
int *t;
t=tablo; // Ou bien t=&tablo[0]
t[5]=0; |
int var;
int *t;
t=&var;
t[0]=12; // Alors là, évidemment, on n'a pas le droit de mettre un autre indice que "0" !!!
|
int *t;
t=malloc(10 * sizeof(int));
t[5]=0;
free(t); |
La règle est valable pour "n" dimensions. T'as pas le droit de taper dans "t[x][y][z]" si "t" ou t[x]" ou "t[x][y]" n'ont pas été alloués auparavant. Or toi, tu vas taper dans "a[i][j]" (équivalent à "*(*(a + i) + j)" ) alors que "a[i]" n'a pas été alloué !!!
exhortae a écrit :
mais bon le problème c'est que j'aimerais une fonctions standard, et ne pas être obligé de spécifier [10 = nombre de colonne]
|
Pas de miracle. Tu déclares "a" comme un pointeur sur un pointeur sur un int (int **a), puis tu alloues à "a" l'espace pour ranger "l" pointeurs sur des int (a=malloc(l * sizeof(int*))) puis tu fais une boucle de 0 à l et tu alloues à chaque "a[i]" l'espace pour ranger c int (a[i]=malloc(c * sizeof(int))).
Là, ce n'est plus un beau carré que tu as mais une pelote de laine où tu peux suivre chaque fil de façon indépendante. Tu pourras donc à loisir aller taper dans "a[i][j]". Le compilo partira de "a" pour aller "i" pointeurs plus loin (tous les éléments de "a" se suivent en mémoire). Ce pointeur ayant été rempli par le a[i]=malloc(...), le compilo pourra aller sur "*a[i]" (<=> a[i][0]) puis, de là, se déplacer sur la case "j" qui se trouve à "j * int" octets plus loin.
N'oublie pas en final, de refaire une boucle de 0 à l et de libérer les "a[i]" avec des "free()" puis, enfin, de libérer "a" par un "free()" final.
---------------
Vous ne pouvez pas apporter la prospérité au pauvre en la retirant au riche.