FAQ C et erreurs courantes

Ce ou ces sujets font partie d'une FAQ en cours d'écriture, vos retours sont les bienvenus.
Retourner un tableau n'est pas possible en C. Le mieux que l'on puisse faire est de retourner une adresse.
Toutefois il faut être très prudent par rapport à la provenance de cette adresse. En effet, un tableau de taille fixe étant implicitement convertible en pointeur, on serait vite tenté de retourner son adresse :
int
*
fonction(void
) {
int
tableau[15
];
// … Traitement …
return
tableau;
}
Mais il faut savoir que ce tableau int
tableau[15
], ainsi que toute variable dite « automatique » ou « locale » déclarée dans la fonction, seront détruits dès que l'on sortira de cette dernière. Cette adresse jusqu'alors occupée par le tableau sera ainsi libérée et pourra dès lors être éventuellement occupée par une variable nouvellement créée ou être utilisée par un autre programme. Toute tentative d'accès ou d'écriture à cette zone mémoire mènera à un comportement non définiQu'est-ce qu'un « comportement indéfini » (Undefined Behaviour / « UB ») ?. Cf. le sujet function returns address of local variablefunction returns address of local variable [-Werror=return-local-addr] .
Il existe quatre solutions :
-
Si le tableau ne doit subir aucun changement d'adresse ou redimensionnement au sein de la fonction, juste ses valeurs qui sont lues/modifiées : passer un tableau déjà existant en paramètre, nous aurons probablement aussi besoin de passer sa taille en même temps.
Code de la fonction Utilisation de la fonction Sélectionnezvoid
fonction(int
*
tableau, size_t taille){
assert(tableau!=
NULL
);// Le pointeur doit être valide
// … Traitement …
}
On peut éventuellement retourner son adresse, ce tableau subsiste après être sorti de la fonction :
Sélectionnezint
*
fonction(int
*
tableau, size_t taille){
assert(tableau!=
NULL
);// Le pointeur doit être valide
// … Traitement …
return
tableau;}
On observe ce comportement dans la fonction standard strcat par exemple.
Sélectionnezint tableau[15]; fonction(tableau, ARRAY_SIZE(tableau)); // Utilisation du tableau
Ou avec un tableau dynamique :
Sélectionnezint
taille=
15
;int
*
tableau=
malloc(taille*
sizeof
(*
tableau));if
(tableau!=
NULL
){
fonction(tableau, taille);// Utilisation du tableau
free(tableau);}
else
{
// Gestion de l'erreur
}
-
Si le tableau doit vraiment être créé dans la fonction, utiliser un tableau dynamique alloué avec la fonction malloc() : la zone mémoire allouée subsistera jusqu'à ce que vous appeliez free dessus.
Code de la fonction Utilisation de la fonction Sélectionnezint
*
fonction(){
int
*
tableau=
malloc(15
*
sizeof
(*
tableau));if
(tableau!=
NULL
){
// … Traitement …
}
return
tableau;}
Sélectionnezint
*
tableau=
fonction();if
(tableau!=
NULL
){
// Utilisation du tableau
free(tableau);// Nous n'avons plus besoin du tableau
}
else
{
// Gestion de l'erreur
}
Vous pourrez avoir besoin de connaître la taille du tableau alloué à l'intérieur de cette fonction, vous pouvez pour ce faire passer un pointeur à la fonction :
Code de la fonction Utilisation de la fonction Sélectionnezint
*
fonction(size_t*
size){
int
*
tableau=
malloc(15
*
sizeof
(*
tableau));if
(tableau!=
NULL
){
*
size=
15
;// … Traitement …
}
return
tableau;}
Sélectionnezsize_t size
=
0
;int
*
tableau=
fonction(&
size);if
(tableau!=
NULL
){
// Utilisation du tableau
free(tableau);// Nous n'avons plus besoin du tableau
}
else
{
// Gestion de l'erreur
}
ou plus simple, utiliser une structure regroupant pointeur et taille qu'il vous suffira de retourner :
Sélectionnezstruct
vector{
int
*
data; size_t size;}
; -
Passer l'adresse d'un pointeur en paramètre, vous aurez alors tout le loisir de le modifier comme bon vous semble :
Code de la fonction Utilisation de la fonction Sélectionnezint
fonction(int
**
ptableau){
assert(ptableau!=
NULL
);// Le double pointeur doit être valide
int
*
tab=
malloc(15
*
sizeof
(*
tableau));if
(tab==
NULL
){
return
EXIT_FAILURE;}
// … Traitement …
*
ptableau=
tab;// Je suppose que *ptableau ne pointe par sur une
// zone allouée ou il faudrait d'abord libérer cet
// emplacement, sinon nous aurions une fuite de mémoire
return
EXIT_SUCCESS;}
Sélectionnezint
*
tableau=
NULL
;if
(!
fonction(&
tableau)){
// Utilisation du tableau
free(tableau);// Nous n'avons plus besoin du tableau
}
else
{
// Gestion de l'erreur
}
-
(Méthode déconseillée : la fonction ne sera pas réentrante) utiliser un tableau
static
dont on renvoie l'adresse :Sélectionnezint
*
fonction(){
static
int
tableau[15
];// … Traitement …
return
tableau;}
À noter qu'étant
static
, le tableau restera le même pendant toute la durée du programme, il ne sera pas renouvelé à chaque appel. -
Dernière méthode, valable pour une chaîne de caractères écrite
"en dur"
dans le programme non modifiable (➝const
nécessaire sous peine d'UBQu'est-ce qu'un « comportement indéfini » (Undefined Behaviour / « UB ») ?, car couramment stockée dans le read-only data segment [ou .rodata]) :Code de la fonction Utilisation de la fonction Sélectionnezchar const * fonction(size_t index) { char const *noms[] = {"Jean", "Martin", "Alexandre", "Jack"}; assert(index < ARRAY_SIZE(noms)); // on s'assure que l'indice ne // dépasse pas la taille du tableau return noms[index]; }
Sélectionnezchar
const
*
nom=
fonction(2
); printf("%s
\n
"
, nom);// Alexandre