web-dev-qa-db-fra.com

Quelle est la différence entre le renvoi d'un caractère * et d'un caractère [] à partir d'une fonction?

Pourquoi la première fonction renvoie-t-elle la chaîne "Hello, World" alors que la seconde fonction ne renvoie rien. Je pensais que la valeur de retour des deux fonctions serait indéfinie car elles renvoient des données hors de portée.

#include <stdio.h>
// This successfully returns "Hello, World"
char* function1()
{
    char* string = "Hello, World!";
    return string;
}
// This returns nothing
char* function2()
{
    char string[] = "Hello, World!";
    return string; 
}

int main()
{
    char* foo1 = function1();
    printf("%s\n", foo1); // Prints "Hello, World"
    printf("------------\n");
    char* foo2 = function2(); // Prints nothing
    printf("%s\n", foo2);
    return 0;
}
66
Tobs

la seconde fonction ne retourne rien

Le tableau string dans la deuxième fonction:

char string[] = "Hello, World!";

a durée de stockage automatique . Il n'existe pas après le retour du flux de contrôle à partir de la fonction.

Considérant que string dans la première fonction:

char* string = "Hello, World!";

pointe vers une chaîne littérale, qui a durée de stockage statique . Cela implique que, la chaîne existe toujours après le retour de la fonction. Ce que vous retournez de la fonction est un pointeur sur cette chaîne littérale.

66
El Profesor

La première chose que vous devez apprendre sur les chaînes est qu'un littéral de chaîne est en réalité un tableau de caractères en lecture seule avec une durée de vie du programme complet. Cela signifie qu'ils ne seront jamais hors de portée, ils existeront toujours tout au long de l'exécution du programme.

La première fonction (function1) renvoie un pointeur sur le premier élément d'un tel tableau.

Avec la deuxième fonction (function2), les choses sont un peu différentes. Ici, la variable string est une variable locale dans la fonction. En tant que tel, il sortira du champ d'application et cessera d'exister une fois que la fonction sera revenue. Avec cette fonction, vous retournez un pointeur sur le premier élément de ce tableau, mais ce pointeur deviendra immédiatement invalide puisqu'il indiquera quelque chose qui n'existe plus. Le fait de le déréférencer (ce qui se produit lorsque vous le passez à printf) mènera au comportement non défini .

26

Une chose très importante à retenir lors du codage en C ou dans d'autres langages basés sur la pile est que, lorsqu'une fonction est retournée, elle (et tout son stockage local) a disparu. Cela signifie que si vous voulez que quelqu'un d'autre puisse voir les résultats de vos méthodes de travail acharné, vous devez le placer quelque part qui existera encore après la cessation de votre méthode, ce qui signifie que vous devez comprendre où C stocke des trucs et comment. 

Vous savez probablement déjà comment fonctionne un tableau en C. Il s’agit simplement d’une adresse mémoire incrémentée de la taille de l’objet et vous savez probablement aussi que C ne vérifie pas les limites, donc si vous voulez accéder au onzième élément d’un dix tableau d'éléments, personne ne va vous arrêter, et tant que vous n'essayez pas d'écrire quoi que ce soit, aucun mal ne sera fait. Ce que vous ignorez peut-être, c'est que C étend cette idée à la manière dont il utilise les fonctions et les variables. Une fonction est simplement une zone de mémoire sur une pile chargée à la demande et le stockage de ses variables est simplement décalé par rapport à cet emplacement. Votre fonction a renvoyé un pointeur sur une variable locale, en particulier l'adresse d'un emplacement sur la pile contenant le 'H' de 'Hello World\n\0', mais lorsque vous avez ensuite appelé une autre fonction (la méthode d'impression), cette mémoire était réutilisé par la méthode d'impression pour faire ce dont il avait besoin. Vous pouvez voir cela assez facilement (NE PAS FAIRE CODE DE PRODUCTION !!!) 

char* foo2 = function2(); // Prints nothing
ch = foo2[0];  // Do not do this in live code!
printf("%s\n", foo2);  // stack used by foo2 now used by print()
printf("ch is %c\n", ch);  // will have the value 'H'!
7
Paul Smith

Je pensais que la valeur de retour des deux fonctions serait indéfinie car elles renvoient des données hors de portée. 

Non, ce n'est pas le cas. 

Dans la fonction function1, vous retournez un pointeur sur un littéral de chaîne. Renvoyer un pointeur sur un littéral de chaîne est acceptable car les littéraux de chaîne ont durée de stockage statique Mais ce n'est pas vrai avec variable locale automatique

Dans la fonction function2, le tableau string est une variable locale automatique et l'instruction 

return string; 

renvoie un pointeur sur une variable locale automatique. Une fois la fonction retournée, la variable string n’existera plus. Le déréférencement du pointeur renvoyé entraînera un comportement indéfini. 

5
haccks

"Hello, World!" est un littéral de chaîne, qui a une durée de stockage statique, le problème est donc ailleurs. Votre première fonction retourne la valeur de string, ce qui est bien. La deuxième fonction renvoie toutefois l'adresse adresse d'une variable locale (string est identique à &string[0]), ce qui entraîne un comportement non défini. Votre deuxième déclaration printf pourrait ne rien imprimer, ou "Hello, World!", Ou autre chose. Sur ma machine, le programme obtient simplement une erreur de segmentation.

Jetez toujours un coup d'œil aux messages de votre compilateur. Pour votre exemple, gcc donne:

file.c:12:12: warning: function returns address of local variable [-Wreturn-local-addr]
    return string; 
           ^

ce qui est assez explicite.

1
Dmitry Grigoryev

Je pensais que la valeur de retour des deux fonctions serait indéfinie car elles renvoient des données hors de portée.

Les deux fonctions renvoient un pointeur. Ce qui compte, c’est la portée du référent.

Dans function1, le référent est le littéral de chaîne "Hello, World!", qui a une durée de stockage statique. string est une variable locale qui pointe vers cette chaîne et conceptuellement, une copie de ce pointeur est renvoyée (en pratique, le compilateur évitera de copier inutilement la valeur).

Dans function2, conceptuellement, le référent est le tableau local string, qui a été automatiquement dimensionné (au moment de la compilation) pour être suffisamment grand pour contenir le littéral de chaîne (y compris un terminateur null, bien sûr) et initialisé avec une copie de la chaîne . La fonction [ renverrait un pointeur sur ce tableau, à l'exception du fait que le tableau a une durée de stockage automatique et qu'il n'existe donc plus après la sortie de la fonction (il est en effet "hors de portée", dans une terminologie plus familière). Comme il s’agit d’un comportement indéfini, le compilateur peut pratiquement faire toutes sortes de choses .

Cela signifie-t-il que tous les char* sont statiques?

Encore une fois, vous devez faire la distinction entre le pointeur et le référent. Les pointeurs pointent vers les données; ils ne "contiennent" pas eux-mêmes les données.

Vous avez atteint un point où vous devez étudier correctement les tableaux et les pointeurs en C - malheureusement, c'est un peu la pagaille. La meilleure référence que je puisse offrir à l’avenir est this , au format Q & R.

0
Karl Knechtel