web-dev-qa-db-fra.com

gcc-8 -Wstringop-truncation quelle est la bonne pratique?

GCC 8 a ajouté un -Wstringop-truncation avertissement. De https://gcc.gnu.org/bugzilla/show_bug.cgi?id=82944 :

L'avertissement -Wstringop-truncation ajouté dans GCC 8.0 via r254630 pour le bogue 81117 est spécifiquement destiné à mettre en évidence les utilisations imprévues probables de la fonction strncpy qui tronquent le caractère NUL final de la chaîne source. Un exemple d'une telle utilisation abusive donnée dans la demande est le suivant:

char buf[2];

void test (const char* str)
{
  strncpy (buf, str, strlen (str));
}

Je reçois le même avertissement avec ce code.

strncpy(this->name, name, 32);

warning: 'char* strncpy(char*, const char*, size_t)' specified bound 32 equals destination size [-Wstringop-truncation`]

Étant donné que this->name est char name[32] et name est un char* d'une longueur potentiellement supérieure à 32. Je voudrais copier name dans this->name et la tronquer si elle est supérieure à 32. Est-ce que size_t avoir 31 ans au lieu de 32? Je suis confus. Ce n'est pas obligatoire pour this->name à terminer par NUL.

16
JRR

Ce message essaie de vous avertir que vous faites exactement ce que vous faites. La plupart du temps, ce n'est pas l'intention du programmeur. Si c'est ce que vous vouliez (ce qui signifie que votre code gérera correctement le cas où le tableau de caractères ne contiendra aucun caractère nul), désactivez l'avertissement.

Si vous ne voulez pas ou ne pouvez pas le désactiver globalement, vous pouvez le désactiver localement comme indiqué par @doron:

#include <string.h>
char d[32];
void f(const char *s) {
#pragma GCC diagnostic Push
#pragma GCC diagnostic ignored "-Wstringop-truncation"
    strncpy(d, s, 32);
#pragma GCC diagnostic pop
}
12
user743382

Il y a très peu de cas justifiés pour utiliser strncpy. C'est une fonction assez dangereuse. Si la longueur de la chaîne source (sans le caractère nul) est égale à la taille du tampon de destination, strncpy n'ajoutera pas le caractère nul à la fin du tampon de destination. Le tampon de destination ne sera donc pas terminé par null.

Nous devrions écrire ce type de code sous Linux:

lenSrc = strnlen(pSrc, destSize)
if (lenSrc < destSize)
    memcpy(pDest, pSrc, lenSrc + 1);
else {
    /* Handle error... */
}

Dans votre cas, si vous souhaitez tronquer la source lors de la copie, mais que vous souhaitez toujours un tampon de destination terminé par null, vous pouvez écrire ce type de code:

destSize = 32

sizeCp = strnlen(pSrc, destSize - 1);
memcpy(pDest, pSrc, sizeCp);
pDest[sizeCp] = '\0';

Edit: Oh ... Si ce n'est pas obligatoire d'être terminé NULL, strncpy est la bonne fonction à utiliser. Et oui, vous devez l'appeler avec 32 et non 31. Je pense que vous devez ignorer cet avertissement en le désactivant ... Honnêtement, je n'ai pas de bonne réponse à cela ...

Edit2: Afin d'imiter la fonction strncpy, vous pouvez écrire ce code:

destSize = 32

sizeCp = strnlen(pSrc, destSize - 1);
memcpy(pDest, pSrc, sizeCp + 1);
2
benjarobin

Ce nouvel avertissement GCC rend strncpy() pratiquement inutilisable dans de nombreux projets: La révision de code n'acceptera pas de code, qui génère des avertissements. Mais si strncpy() est utilisé uniquement avec des chaînes suffisamment courtes, de sorte qu'il puisse écrire l'octet zéro de fin, puis la mise à zéro du tampon de destination au début, puis le simple strcpy() atteindrait le même emploi.

En fait, strncpy() est l'une des fonctions qu'il vaut mieux ne pas mettre dans la bibliothèque C. Il existe des cas d'utilisation légitimes pour cela, bien sûr. Mais les concepteurs de bibliothèques ont oublié de mettre des équivalents sensibles aux chaînes de taille fixe à strncpy() dans la norme également. Les fonctions les plus importantes, strnlen() et strndup(), n'ont été incluses qu'en 2008 dans POSIX.1, des décennies après la création de strncpy()! Et il n'y a toujours pas de fonction, qui copie une chaîne de longueur fixe générée par strncpy() dans un tampon préalloué avec la sémantique C correcte, c'est-à-dire toujours en écrivant l'octet de terminaison 0. Une de ces fonctions pourrait être:

// Copy string "in" with at most "insz" chars to buffer "out", which
// is "outsz" bytes long. The output is always 0-terminated. Unlike
// strncpy(), strncpy_t() does not zero fill remaining space in the
// output buffer:
char* strncpy_t(char* out, size_t outsz, const char* in, size_t insz){
    assert(outsz > 0);
    while(--outsz > 0 && insz > 0 && *in) { *out++ = *in++; insz--; }
    *out = 0;
    return out;
}

Je recommande d'utiliser deux entrées de longueur pour strncpy_t(), pour éviter toute confusion: s'il n'y avait qu'un seul argument size, ce ne serait pas clair, si c'est la taille du tampon de sortie ou le longueur maximale de la chaîne d'entrée (qui est généralement une de moins).

2
Kai Petzke

J'ai trouvé que le meilleur moyen de supprimer l'avertissement est de mettre l'expression entre parenthèses comme ce patch gRPC :

(strncpy(req->initial_request.name, lb_service_name,
         GRPC_GRPCLB_SERVICE_NAME_MAX_LENGTH));

Le problème avec #pragma la solution de suppression des diagnostics est que le #pragma lui-même provoquera un avertissement lorsque le compilateur ne reconnaît ni le pragma ni l'avertissement particulier; il est aussi trop verbeux.

0
Mehrdad Afshari