web-dev-qa-db-fra.com

Pointeurs de caractères et la fonction printf

J'essayais d'apprendre des pointeurs et j'ai écrit le code suivant pour imprimer la valeur du pointeur:

#include <stdio.h>

    int main(void) {
        char *p = "abc";
        printf("%c",*p);
        return 0;
    }

La sortie est:

une

cependant, si je change le code ci-dessus en:

#include <stdio.h>

int main(void) {
    char *p = "abc";
    printf(p);
    return 0;
}

J'obtiens la sortie:

abc

Je ne comprends pas les 2 choses suivantes:

  1. pourquoi printf n'avait-il pas besoin d'un spécificateur de format dans le second cas? printf(pointer_name) suffit-il pour imprimer la valeur du pointeur?

  2. selon ma compréhension (qui est très peu), * p pointe vers un bloc de mémoire contigu qui contient abc. Je m'attendais à ce que les deux sorties soient identiques, c'est-à-dire.

abc

sont les différentes sorties en raison des différentes façons d'imprimer?

Modifier 1

En outre, le code suivant génère une erreur d'exécution. Pourquoi

 #include <stdio.h>

    int main(void) {
        char *p = "abc";
        printf(*p);
        return 0;
    }
10
Fabulous

Pour votre première question, la fonction printf (et famille) prend une chaîne comme premier argument (c'est-à-dire un const char *). Cette chaîne peut contenir des codes de format que la fonction printf remplacera par l'argument correspondant. Le reste du texte est imprimé tel quel, mot pour mot. Et c'est ce qui se passe lorsque vous passez p comme premier argument.

Notez que l'utilisation de printf de cette façon est fortement déconseillée, surtout si la chaîne contient des entrées d'un utilisateur. Si l'utilisateur ajoute des codes de mise en forme dans la chaîne et que vous ne fournissez pas les arguments corrects, vous aurez un comportement indéfini . Cela pourrait même entraîner des failles de sécurité.

Pour votre deuxième question, la variable p pointe vers de la mémoire. L'expression *p Déréférence le pointeur pour vous donner un seul caractère, à savoir celui vers lequel p pointe réellement, qui est p[0].

Pensez à p comme ceci:

 + --- + + ----- + ----- + ----- + ------ + 
 | p | ---> | "a" | "b" | "c" | '\ 0' | 
 + --- + + ----- + ----- + ----- + ------ + 

La variable p ne pointe pas vraiment vers une "chaîne", elle ne pointe que vers un emplacement unique en mémoire, à savoir le premier caractère de la chaîne "abc". Ce sont les fonctions utilisant p qui traitent cette mémoire comme une séquence de caractères.

En outre, les littéraux de chaîne constants sont en fait stockés sous forme de tableaux (en lecture seule) du nombre de caractères dans la chaîne plus un pour le terminateur de chaîne.

De plus, pour vous aider à comprendre pourquoi *p Est identique à p[0], Vous devez savoir que pour tout pointeur ou tableau p et un index valide i, les expressions p[i] sont égales à *(p + i). Pour obtenir le premier caractère, vous avez l'index 0, Ce qui signifie que vous avez p[0] Qui doit alors être égal à *(p + 0). L'ajout de zéro à n'importe quoi est un no-op, donc *(p + 0) est identique à *(p) qui est identique à *p. Par conséquent, p[0] Est égal à *p.


Concernant votre édition (où vous faites printf(*p)), puisque *p Renvoie la valeur du premier "élément" pointé par p (ie p[0]) vous passez un caractère unique comme pointeur vers la chaîne de format. Cela conduira le compilateur à le convertir en un pointeur qui pointe vers n'importe quelle adresse ayant la valeur de ce caractère unique (il ne convertit pas le caractère en pointeur en le caractère). Cette adresse n'est pas une adresse très valide (dans alphabet ASCII'a' A la valeur 97 Qui est l'adresse où le programme recherchera la chaîne à imprimer) et vous aurez un comportement indéfini .

16
  1. p est la chaîne de format.

    char *p = "abc";
    printf(p);
    

    est le même que

    print("abc");
    

    Faire cela est une très mauvaise pratique car vous ne savez pas ce que contiendra la variable, et si elle contient des spécificateurs de format, appeler printf peut faire de très mauvaises choses.

  2. La raison pour laquelle le premier cas (avec "%c") uniquement imprimé le premier caractère est que %c signifie un octet et *p signifie la (première) valeur sur laquelle p pointe.

    %s afficherait la chaîne entière.

    char *p = "abc";
    printf(p); /* If p is untrusted, bad things will happen, otherwise the string p is written. */
    printf("%c", *p); /* print the first byte in the string p */
    printf("%s", p); /* print the string p */
    
8
Oskar Skog
  1. pourquoi printf n'avait-il pas besoin d'un spécificateur de format dans le second cas? Printf (pointer_name) suffit-il pour imprimer la valeur du pointeur?

"abc" is votre spécificateur de format. C'est pourquoi il imprime "abc". Si la chaîne contenait %, alors les choses se seraient étrangement comportées, mais elles ne l'ont pas fait.

printf("abc"); // Perfectly fine!
  1. pourquoi printf n'avait-il pas besoin d'un spécificateur de format dans le second cas? Printf (pointer_name) suffit-il pour imprimer la valeur du pointeur?

%c est le spécificateur de conversion de caractères. Il demande à printf de n'imprimer que le premier octet. Si vous voulez qu'il imprime la chaîne, utilisez ...

printf ("%s", p);

Le %s semble redondant, mais il peut être utile pour imprimer des caractères de contrôle ou si vous utilisez des spécificateurs de largeur.


La meilleure façon de comprendre cela est d'essayer d'imprimer la chaîne abc%def en utilisant printf.

2
QuestionC

pourquoi printf n'avait-il pas besoin d'un spécificateur de format dans le second cas? Printf (pointer_name) suffit-il pour imprimer la valeur du pointeur?

Avec votre code, vous avez dit à printf d'utiliser votre chaîne comme chaîne de format. Cela signifie que votre code est devenu équivalent à printf("abc").

selon ma compréhension (qui est très peu), * p pointe vers un bloc de mémoire contigu qui contient abc. Je m'attendais à ce que les deux sorties soient les mêmes

Si vous utilisez %c Vous obtenez un caractère imprimé, si vous utilisez %s Vous obtenez une chaîne entière. Mais si vous dites à printf d'utiliser la chaîne comme chaîne de format, il le fera aussi.

char *p = "abc"; printf(*p);

Ce code se bloque parce que le contenu de p, le caractère 'a' N'est pas un pointeur vers une chaîne de format, ce n'est même pas un pointeur. Ce code ne devrait même pas être compilé sans avertissements.

2
Lundin

Vous vous méprenez, en effet quand vous le faites

char *p = "Hello";

p pointe vers l'adresse de départ où le littéral "Hello" est stocké. C'est ainsi que vous déclarez pointeurs. Cependant, quand après, vous

*p

cela signifie déréférencep et obtenir l'objet où p pointe. Dans notre exemple ci-dessus, cela donnerait "H". Cela devrait clarifier votre deuxième question.

En cas d'impression, essayez

printf("Hello");

ce qui est également très bien; cela répond à votre première question car il s'agit en fait de la même chose que lorsque vous avez passé juste p à printf.


Enfin à votre montage, en effet

printf(*p);

la ligne ci-dessus n'est pas correcte car printf attend const char * et en utilisant *p vous lui passez un char - ou en d'autres termes 'H' en supposant notre exemple. En savoir plus sur le déréférencement.

2
giorgim

Le %c Le spécificateur de format attend un type char et affichera une valeur niquechar.

Le premier paramètre de printf doit être un const char* (une char* peut se convertir implicitement en const char*) et pointe vers le début d'un chaîne de caractères. Il arrête d'imprimer lorsqu'il rencontre un \0 dans cette chaîne. S'il n'y a pas de \0 présent dans cette chaîne, le comportement de cette fonction est non défini. Car "abc" ne contient aucun spécificateur de format, dans ce cas, vous ne passez aucun argument supplémentaire à printf.

1
Bathsheba