2010-06-30 4 views
3

J'ai un void *, appelez-le data, dont je connais la longueur, mais qui n'est pas null terminé. Je fais un appel comme ceci snprintf(line, sizeof(line), "%*s", n, (const char*)data)n est la longueur connue. Presque toujours cela fonctionne, mais parfois il en résulte une erreur de segmentation.Est-ce qu'un appel strlen dans snprintf provoque cette erreur de segmentation?

Chaque fois que segfault se produit, la trace arrière indique que le problème se trouve dans strlen. Et quand j'imprimer data gdb, je vois quelque chose comme ça

(gdb) p n 
$1 = 88 
(gdb) p (const char*) data 
$2 = 0x1d752fa8 
"JASDF" ... "ADS"<Address 0x1d753000 out of bounds> 
(gdb) p 0x1d753000-0x1d752fa8 
$3 = 88 

data est en effet 88 caractères, mais est nulle fin pas, en fait, il semble qu'il se trouve tout contre un segment. Ma conjecture est que snprintf est toujours appelé strlen sur les données et j'ai l'habitude d'être chanceux car même si data n'est pas terminé, il y a un \0 avant que j'atteigne le segment et parfois j'ai malchance et ça l'est. Est-ce correct? Si oui, quel est le travail?

C'est ce que la trace de la pile ressemble

#0 0x0000003c8927839e in strlen() from /lib64/libc.so.6 
#1 0x0000003c89246749 in vfprintf() from /lib64/libc.so.6 
#2 0x0000003c8926941a in vsnprintf() from /lib64/libc.so.6 
#3 0x0000003c8924d0a3 in snprintf() from /lib64/libc.so.6 

EDIT Pour répondre à ma propre question sur le travail autour, strncpy est une fonction plus approprié d'appeler. J'ai utilisé snprintf par habitude.

+0

Comment 'line' est-il déclaré? –

+0

'ligne char [256];' –

Répondre

7

snprintf(line, sizeof(line), "%*s", n, (const char*)data)

data est pas mis fin à zéro? Alors vous le faites mal.

snprintf(line, sizeof(line), "%.*s", n, (const char*)data); 

Notez le point.

Dans le cas des chaînes, la première * dans le *.* si pour la sortie désirée (à l'écran) longueur - la longueur d'entrée est la deuxième . man printf pour plus d'informations.

Et évidemment dans le cas de %*s le formatage peut appeler strlen(), car il a besoin de savoir s'il doit tamponner la sortie et comment le pad.

+0

Vous avez raison. La page de manuel est assez explicite dans la discussion des chaînes de terminaison non nulles et du modificateur de précision. –

6

On dirait que vous avez raison. Il n'y a aucune garantie que printf n'appellera pas strlen, même si cela n'est pas obligatoire dans un contexte donné. Vous résidez en fournissant quelque chose qui n'est pas une chaîne C en tant que paramètre pour un spécificateur de formatage %s, et donc vous avez rompu le contrat de printf. Résultats de comportement non définis.

+0

'% s' prend en charge les chaînes non-0 terminées:'%.* s' – Dummy00001

+3

Le vrai problème est que sa chaîne de format est incorrecte, utilisez '"%. * s "' au lieu de ''% * s "' – nos

1

Je pense que votre analyse est correcte. Si le tampon n'est pas terminé, l'appel strlen est lu jusqu'à ce qu'il trouve un \0. S'il dépasse la fin du segment (et que le segment suivant n'est pas valide), il produira une exception.

La solution consiste soit à l'annuler soit à le mettre dans un autre tampon que vous pouvez terminer par un caractère nul.

+0

voir ma réponse ci-dessous. le formatage prend déjà en charge les chaînes non-0 terminées. – Dummy00001

0

La "solution" est d'utiliser memcpy():

size_t validlen = n < sizeof line ? n : (sizeof line - 1); 
memcpy(line, data, validlen); 
line[validlen] = '\0'; 
+0

"%. * S" est le moyen de le faire - aucune copie redondante n'est requise. – Dummy00001

+0

Il n'y a pas de copie redondante dans ma réponse - il n'y a qu'une copie * simple *, en utilisant 'memcpy()' au lieu de 'snprintf()'. – caf

Questions connexes