2010-02-26 4 views
5

Je suis tombé sur de nombreuses fonctions renvoyant des pointeurs char dans une application héritée. Certains d'entre eux retournent des pointeurs vers des tableaux de caractères locaux. Il semble causer des accidents après plusieurs invocations (pas tout de suite!) Voir l'utilisation ci-dessousfonctions renvoyant pointeur char

char *f1(){ 
    char buff[20]; 
    char *ptr; 

    ---- 
    ---- 
    ptr=buff; 
return ptr; 
} 

--- 
--- 

f2(f1()); 

f1() retourne une variable locale de pointeur, puis passe à une autre fonction. J'ai eu l'accident directement quand il est compilé en utilisant le mode _DEBUG dans MS DEV. Mais en mode release, il ne provoque pas un crash immédiat, mais il peut se produire après avoir fait beaucoup d'appels de ce type.

Lorsque j'ai modifié l'utilisation comme ci-dessous, cela fonctionne sans aucun problème. L'utilisation suivante est-elle sûre?

strcpy(arr,f1()); /* arr is fixed char array*/ 
f2(arr); 

Répondre

12

Non, il s'agit d'un comportement non défini. Il arrive juste de travailler dans votre cas, mais peut cesser de travailler à tout moment.

+1

+1 à peu près tout. – Tom

+0

Et la raison pour laquelle cela arrive est que sur le x86 le tableau buff est rempli depuis les adresses les plus basses vers les adresses plus élevées et ne sera probablement pas touché par l'utilisation de la pile de strcpy. Ne le faites pas de toute façon: l'utilisation de la pile peut varier en raison d'événements asynchrones tels que les signaux et les interruptions. –

3

Non, ce n'est pas sûr. Le simple appel de strcpy peut modifier suffisamment la pile pour causer des problèmes plus tard car l'adresse de retour et les paramètres peuvent écraser le tableau.

2

La fonction f1 renvoie un buff temporaire qui est libéré lorsque la fonction retourne. Vous devez utiliser malloc() dans la fonction.

+0

Cela pourrait presque garantir une fuite de mémoire avec une référence non-repérée à la fonction. –

+0

Vous pouvez bien sûr avoir besoin d'un pointeur sur la mémoire préallouée en tant que paramètre d'entrée et avoir la fonction la remplir, mais nous n'avons plus la même signature de fonction. – Zitrax

1

Non .. ce n'est toujours pas sécuritaire.

Au moment où vous faites strcpy(arr,f1());, le pointeur utilisé comme 2ème argument pointe déjà vers un tableau qui n'existe pas.

0

En fait, le mieux serait de modifier f1() pour utiliser malloc(). Votre solution n'est pas proche du comportement défini.

+1

ok, alors tous les appelants à f1() devraient correctement le libérer après son utilisation. droite? – cobp

+0

Oui, ils devraient. – moatPylon

2

Ne jamais renvoyer un pointeur sur une variable locale. Cela peut fonctionner dans certaines situations, mais en général vous aurez beaucoup de problèmes avec cela. Au lieu de cela:

  • Documentez votre fonction qu'il renvoie un pointeur sur la mémoire allouée et que le tampon de retour doit être libéré par l'appelant
  • ajouter un tampon et un argument de taille, et remplir le tampon (ce qui est ce qui se fait généralement dans l'API Win32)
  • si vous utilisez C++, utilisez std :: string
+0

Je pense que deux solutions pour cela. 1. utilisez malloc à l'intérieur de f1(). Dans ce cas, tous les appelants devraient libérer de la mémoire correctement après son utilisation. des utilisations comme f2 (f1()) doivent être évitées, car il ne rétrogradera pas le handle pour libérer de la mémoire. 2. en passant un ptr alloué à la fonction, afin que f1() puisse y copier le texte. les usages existants comme f2 (f1()) ne sont pas possibles dans ce cas également. quelle solution préférez-vous? Quoi qu'il en soit, il faut beaucoup de changements dans l'application existante. parce que de tels modèles existent à plusieurs endroits. certains d'entre eux allouent de la mémoire, mais ne la libèrent pas. Les objets comme f1 (f2 (f3())) existent. 2. – cobp

+0

Si par "jamais" vous voulez dire "à moins que la variable locale soit statique", alors je suis d'accord. –

+0

@William, si la variable locale est statique, elle fonctionnera (au moins pour un temps plus long). Mais cela ne signifie pas que c'est une bonne pratique. Si la fonction est ré-exécutée avant que vous ayez fait une copie personnelle du tampon retourné, vous aurez toujours des problèmes. Similaire pour les applications multithread (bien que vous puissiez utiliser des variables Thread-Local-Storage pour cela). – Patrick

2

est l'utilisation suivante sans danger?

No.

Si votre fonction renvoie un pointeur sur quoi que ce soit, assurez-vous qu'il alloue une zone mémoire et renvoie le pointeur vers elle:

char *safe_f1(void) { 
    char *ptr; 
    ptr = (char *) malloc(20 * sizeof(char)); 
    ... 
    return ptr; 
} 
1

Non, vous voyez buff[20] est uniquement disponible à l'intérieur de la fonction f1. Pour être précis, la mémoire est allouée sur la pile f1.

Vous devez créer un nouveau buff[20] sur le tas en utilisant malloc et renvoyer un pointeur vers cette mémoire depuis l'intérieur f1.Une autre méthode consiste à créer buff[20] en dehors de f1 (à partir de la fonction qui appelle f1) et l'envoyer en tant qu'argument à f1. f1 peut alors changer le contenu du tampon et n'a même pas à le renvoyer.

3

Les solutions malloc sont intéressantes sauf que la mémoire devrait être libre après utilisation. (en dehors de la fonction). Sinon, il y aurait une fuite de mémoire.

2

Ce n'est pas sûr. La raison est simple:

Toute variable sur une fonction sera allouée sur la pile dont la mémoire est libérée après le retour de la fonction. Le fait que la mémoire soit libérée ne signifie pas que son contenu est changé.

Cela signifie que le contenu de la mémoire que vous avez mis dans la char buff[20] variable est toujours à la position de mémoire buff ou ptr (depuis ptr=buff). Chaque fois que vous appelez une autre fonction (ou que vous exécutez un autre bloc), ses variables de fonction/bloc vont également dans la pile, créant ainsi la possibilité de changer le contenu de la mémoire pour lequel pointe la position . Dans l'exemple strcpy de l'exemple strcpy, vous avez eu la chance que les variables de fonction strcpy ne soient pas sur la pile à une position qui se trouvait dans l'ancien tableau buff. C'est la raison pour laquelle vous avez eu l'impression que c'était sûr.

La conclusion est qu'il est impossible de garantir qu'un contenu mémoire libéré de la pile ne changera pas entre deux appels de fonction.

La solution consiste à utiliser malloc car malloc n'alloue pas de mémoire sur la pile mais sur le tas. La mémoire de tas n'est pas libérée sauf si vous avez choisi de le faire (par un appel gratuit). Cette approche garantirait que la mémoire pointée par ptr peut être utilisée en toute sécurité par n'importe quelle autre fonction. L'inconvénient de cette solution est intrinsèque: une fois que la mémoire n'est libérée que si vous le faites par programmation, si vous oubliez de libérer cette mémoire et de perdre le contenu de ptr, cette mémoire sera là, allouée à votre programme mais jamais réalisable, perdu pour aussi longtemps que votre programme s'exécute. Cette mémoire deviendra une fuite de mémoire :-)

C'est une raison pour laquelle certaines langues ont éboueurs ... mais cela est une autre histoire :-)

PS .: Je pense qu'il est sûr (si votre programme est un seul thread), bien que je ne recommande pas, de faire quelque chose comme ça:

{ 
    char safe_buffer[20]; 
    char *unsafe_ptr; 
    int i; 
    unsafe_ptr = f1(); 
    /*Copy the buffer without calling any function 
    not to change the stack content 
    */ 
    for(i=0;i<20 && *(unsafe_ptr + i) != 0;i++) 
    { 
    *(safe_buffer + i) = *(unsafe_ptr + i); 
    } 
    *(safe_buffer + i) = 0; 
    f2(safe_buffer); 
} 
0

Je vous suggère deux solutions possibles:

  1. Utilisez unstatiquedans f1 sauf si la fonction est appelée à partir de plusieurs threads ou si le monde extérieur stocke le pointeur au-delà de strcpy.

  2. Utilisez return strdup (ptr); et free le pointeur en dehors de f1. C'est plus facile à utiliser que malloc (bien que techniquement identique). C'est plus lent que 1. mais thread sécurisé.

0

Je suggère de modifier ces fonctions pour prendre un pointeur qu'il utilise

void f1(char *) 

De cette façon, chaque morceau de code appelant la fonction doit prendre une décision sur l'endroit où la mémoire est écrit, et pour supprimer toute mémoire allouée.

Questions connexes