37 votes

Référencement d'un caractère * hors de portée

J'ai récemment commencé la programmation en C de nouveau après avoir programmé en C++ pour un certain temps, et ma compréhension des pointeurs est un peu rouillé.

Je voudrais demander pourquoi ce code n'est pas la cause des erreurs:

char* a = NULL;
{
    char* b = "stackoverflow";
    a = b;
}

puts(a);

J'ai pensé que, parce qu' b sont allés hors de portée, a devrait faire référence à un non-existant emplacement de mémoire, et donc leur serait une erreur d'exécution lors de l'appel d' printf.

J'ai couru ce code dans MSVC environ 20 fois, et aucune erreur n'a été montré.

47voto

dbush Points 8590

À l'intérieur de la portée où l' b est défini, il est affecté à l'adresse d'une chaîne littérale. Ces littéraux vivent généralement en lecture seule section de la mémoire, par opposition à la pile.

Lorsque vous effectuez a=b vous affectez la valeur de b de a, c'est à dire a contient maintenant l'adresse d'une chaîne littérale. Cette adresse est toujours valide après l' b est hors de portée.

Si vous aviez pris l' adresse de l' b et a ensuite tenté de déréférencement de cette adresse, alors vous appelleriez un comportement indéfini.

Si votre code est valide et ne pas invoquer un comportement indéfini, mais le suivant n':

int *a = NULL;
{
    int b = 6;
    a = &b;
}

printf("b=%d\n", *a);

Une autre, plus subtile, par exemple:

char *a = NULL;
{
    char b[] = "stackoverflow";
    a = b;
}

printf(a);

La différence entre cet exemple et le vôtre est qu' b, ce qui est un tableau, se désintègre à un pointeur vers le premier élément lorsqu' a. Dans ce cas - a contient l'adresse d'une variable locale qui est hors de portée.

EDIT:

Comme une note de côté, c'est une mauvaise pratique de passer une variable en tant que premier argument de printf, car cela peut conduire à une vulnérabilité de chaîne de format. Mieux vaut utiliser une constante de chaîne comme suit:

printf("%s", a);

Ou plus simplement:

puts(a);

12voto

SiggiSv Points 1150

Ligne par ligne, c'est ce que votre code:

char* a = NULL;

a est un pointeur pas référence à quoi que ce soit (mis à NULL).

{
    char* b = "stackoverflow";

b est un pointeur référençant la statique, de la constante chaîne de caractères littérale "stackoverflow".

    a = b;

a est fixé à également référence à la statique, de la constante chaîne de caractères littérale "stackoverflow".

}

b est hors de portée. Mais depuis a est pas le référencement b, alors ce n'est pas grave (c'est juste référence à la même statique, une constante chaîne de caractères littérale comme b a été référencement).

printf(a);

Imprime la statique, de la constante chaîne de caractères littérale "stackoverflow" référencé par a.

11voto

kyle Points 454

Les littéraux de chaîne étant alloués de manière statique, le pointeur est valide indéfiniment. Si vous aviez déclaré char b[] = "stackoverflow" , vous alloueriez un tableau de caractères sur la pile qui deviendrait invalide à la fin de la portée. Cette différence apparaît également pour la modification des chaînes: char s[] = "foo" pile alloue une chaîne que vous pouvez modifier, alors que char *s = "foo" vous donne uniquement un pointeur sur une chaîne pouvant être placée en mémoire en lecture seule. , donc le modifier est un comportement indéfini.

9voto

Zack Points 44583

D'autres personnes ont expliqué que ce code est parfaitement valide. Cette réponse est sur votre vœu que, si le code était invalide, il y aurait eu une erreur d'exécution lors de l'appel d' printf. Il n'est pas nécessairement le cas.

Regardons cette variation sur votre code, ce qui est invalide:

#include <stdio.h>
int main(void)
{
    int *a;
    {
        int b = 42;
        a = &b;
    }
    printf("%d\n", *a); // undefined behavior
    return 0;
}

Ce programme a un comportement indéfini, mais il arrive à être assez probable qu'il y aura, en fait, d'impression 42, pour plusieurs raisons différentes - de nombreux compilateurs laisser la pile fente b alloué pour l'ensemble du corps de l' main, parce que rien d'autre n'a besoin de l'espace et de réduire au minimum le nombre de pile ajustements simplifie la génération de code; même si le compilateur n'a officiellement libérer le logement de la pile, le nombre 42 probablement reste en mémoire jusqu'à ce que quelque chose d'autre la remplace, et il n'y a rien entre les deux, a = &b et *a à le faire; norme optimisations ("constant et copie de propagation") pourrait éliminer à la fois les variables et écrire la dernière valeur connue de *a directement dans l' printf déclaration (comme si vous l'aviez écrit, printf("%d\n", 42)).

Il est absolument essentiel de comprendre que le "comportement indéfini" ne signifie pas "le programme va planter prévisible". Il signifie "tout peut arriver", et tout ce qui comprend apparaissant à travailler en tant que programmeur probablement prévu (sur cet ordinateur, avec ce compilateur, aujourd'hui).


Comme note finale, aucun des agressifs, des outils de débogage, j'ai un accès pratique à (Valgrind, ASan, UBSan) piste "auto", la variable de la durée de vie de façon suffisamment détaillée pour intercepter cette erreur, mais GCC 6 ne produire cet amusant d'avertissement:

$ gcc -std=c11 -O2 -W -Wall -pedantic test.c
test.c: In function ‘main':
test.c:9:5: warning: ‘b' is used uninitialized in this function
    printf("%d\n", *a); // undefined behavior
    ^~~~~~~~~~~~~~~~~~

Je crois que ce qui s'est passé ici était, il a fait de l'optimisation que j'ai décrit ci-dessus - la copie de la dernière valeur connue de l' b en *a , puis dans l' printf - , mais sa "dernière valeur connue" pour b était une "cette variable est initialisée" sentinelle plutôt que de 42. (Puis, il génère un code équivalent à printf("%d\n", 0).)

2voto

Jay Patel Points 960

Le code ne génère pas d'erreur parce que vous êtes tout simplement l'attribution pointeur de caractère b à un autre pointeur de caractère a et qui est parfaitement bien.

En C, Vous pouvez affecter un pointeur de référence à un autre pointeur. là, effectivement, la chaîne "stackoverflow" est utilisé comme un littéral et l'adresse de base de l'emplacement de cette chaîne sera attribuer à l' a variable.

Si vous êtes hors de portée de variable b mais encore la cession a été fait avec l' a pointeur. Donc, il va imprimer le résultat sans aucune erreur.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X