1
votes

Comment faire en sorte que le compilateur vérifie une longueur de tableau?

J'ai défini une fonction:

char st[5];
myFunction(st);

L'appel de la fonction avec

void myFunction(char string[20])
{
    // update my string here, maximal size is 20.
}

est une erreur, car myFunction code > écrase la pile ce qui conduit à un comportement indéfini. Bien sûr, st doit être de taille 20 (ou plus).

Existe-t-il un moyen de faire se plaindre le compilateur c lorsque de telles erreurs se produisent, et quelle est la méthode recommandée pour coder de tels appels?


2 commentaires

La seule façon de connaître la longueur du tableau (qui en tant qu'argument de fonction est en fait un pointeur) est de passer la longueur comme un autre argument. Le compilateur ignorera le 20 dans la définition: il n'est utilisé que pour un tableau multidimensionnel afin que le compilateur sache comment l'indexer, par exemple char string [] [20] < / code>. Il est de l'entière responsabilité du programmeur en C de ne pas briser les limites du tableau (sauf pour l'initialisation des données, dans le cadre de la définition du tableau).


La méthode habituelle qui aide est de définir le nombre / longueur et de l'utiliser partout (par exemple): #define COUNT 20 et faire: char st [COUNT]; et void maFonction (chaîne de caractères [COUNT]) {for (int i = 0; i . Ce n'est pas exactement ce que vous demandez, mais c'est mieux que de câbler les décomptes dans tous les endroits. Il permet également de changer facilement le nombre (par exemple #define COUNT 37 ) et tous les emplacements sont ajustés automatiquement.


3 Réponses :


3
votes

Bien sûr, st doit être de taille 20 (ou plus).

Vous pouvez utiliser la fonction d'index de tableau statique de C99, disponible pour une utilisation dans déclarateurs de fonction .

Si le mot-clé static apparaît également dans les [et] de la dérivation de type tableau, alors pour chaque appel à la fonction, la valeur de l'argument réel correspondant doit donner accès au premier élément d'un tableau avec au moins autant de éléments tels que spécifiés par l'expression de taille.

Vous pouvez donc déclarer la fonction comme

void myFunction(char string[static 20])
{
    /* ... */
}

à la place. Cela déclenchera un avertissement du compilateur si l'argument de tableau passé contient moins de 20 éléments.

Par exemple, lorsque vous appelez maFonction avec st de type char [5] , clang 10 me donne avertissement: l'argument du tableau est trop petit; contient 5 éléments, l'appelé nécessite au moins 20 [-Warray-bounds]

Cependant, pour l'instant, seul clang semble avoir le support avec -Warray-bounds , et GCC prévoit d'ajouter le support dans le futur . p >

Notez que cela ne fonctionne qu'au moment de la compilation pour les tableaux, mais cela en avertit également un lors du passage de pointeurs NULL (si cela peut être déterminé au moment de la compilation). Cependant, quelqu'un peut passer un pointeur vers un tableau d'éléments alloué dynamiquement inférieur à 20, et le compilateur ne peut pas le vérifier statiquement au moment de la compilation.

Donc, dans ce cas, cela ne fonctionne bien que lorsque le type déclaré de l'argument est char [N] où N est sa taille, et N est au moins 20. Si vous souhaitez gérer les deux cas, un paramètre de taille explicite sera nécessaire, et une vérification à l'intérieur de la fonction pour retourner une erreur dans le cas où la taille passée est inférieure à 20.


3 commentaires

Cela peut déclencher un avertissement du compilateur. Le standard C ne nécessite pas d'implémentation pour diagnostiquer cela, et un compilateur peut ou non même s'il peut voir la dimension du tableau utilisé comme argument. Et, bien sûr, selon les circonstances, comme vous le mentionnez, le compilateur peut ne pas être en mesure de voir cela, comme lorsqu'il passe un pointeur reçu d'ailleurs ou calculé de manière compliquée. Par exemple, Apple Clang 11.0 le diagnostique lorsque myFunction (st) est utilisé mais pas lorsque myFunction (st + 1) est utilisé.


@EricPostpischil Correct. Tout ce que la définition d'un paramètre comme char string [static 20] plutôt que char string [20] provoque tout appel qui ne passe un pointeur vers l'élément initial d'un tableau d'au moins 20 éléments pour avoir un comportement indéfini. Il permet des avertissements, mais un compilateur conforme pourrait ignorer silencieusement le static (et le 20 ).


Bien, mais notez que la question d'origine pose deux exigences, que le compilateur vérifie la taille du tableau passé au moment de la compilation, et que ce soit au moins 20 (la première citation), tous deux de qui ne sont possibles qu'avec l'utilisation de static et un compilateur implémentant des avertissements. Ainsi, alors que c'est vrai, la norme ne nécessite pas un compilateur conforme pour implémenter les avertissements, il est utile de l'utiliser pour ceux qui le font.



2
votes

Vous pouvez encapsuler le tableau:

void myFunction( char (*string)[20] )
{
}
//....
char st[5];
char st20[20];
myFunction(&st); //WRONG; diagnosed error
myFunction(&st20); //OK

ou passer des pointeurs vers le tableau (! = pointeurs vers le premier élément):

struct string20 { char string[20]; };
void myFunction(struct string20 *string)
{
}

En utilisant l'une de ces approches, vous obtiendrez des diagnostics du compilateur quelle que soit votre plate-forme (tant qu'il s'agit d'une plate-forme conforme à la norme).


0 commentaires

1
votes

Votre fonction ressemble à ceci:

char st[5];
myFunction(st);

Mais, le compilateur la voit comme ceci:

void myFunction(char *string)
{
    // update my string here, maximal size is 20.
}

La taille "20" signifie littéralement rien au compilateur. Vous pouvez appeler la fonction avec un tableau de n'importe quelle longueur.

Concernant l'appel avec:

void myFunction(char string[20])
{
    // update my string here, maximal size is 20.
}

C'est tout à fait correct.

Si vous voulez appliquer une taille de tableau spécifique, vous pouvez envelopper le tableau dans une structure.


4 commentaires

La taille «20» ne signifie littéralement rien pour le compilateur, et le compilateur ne voit pas une déclaration de paramètre de tableau uniquement comme une déclaration de paramètre de pointeur. Bien que la norme C spécifie que les déclarations de paramètres de tableau sont ajustées aux déclarations de pointeur, la déclaration est soumise à des contraintes et à un traitement avant cet ajustement. Par exemple, void MyFunction (char string [20] []) produira une erreur car le type d'élément du tableau est incomplet, mais void MyFunction (char (* string) []) ne le fera pas.


Et un appel à void MyFunction (char string [met ("foo")]) peut afficher "foo" (fait dans GCC et Clang, si je me souviens bien, mais le standard C n'est pas clair sur le comportement requis), l'affichage de la déclaration du tableau a des effets en plus d'être simplement une déclaration de pointeur.


@EricPostpischil, le standard C dit "Si la taille est une expression qui n'est pas une expression constante entière: si elle apparaît dans une déclaration à la portée du prototype de fonction, elle est traitée comme si elle était remplacée par *". Je suppose que cela signifie qu'une implémentation conforme n'évaluera pas l'appel met dans votre exemple.


@JohnBollinger: La portée du prototype de fonction est pour les déclarations de non-définition uniquement, selon C 2018 6.2.1 4. Dans une définition de fonction, la déclaration de paramètre a une portée de bloc.