Pourquoi cette fonction renvoie-t-elle la longueur correcte d'une chaîne? (Incrémentation d'un pointeur char)

12

Il s'agit d'une fonction qui compte le nombre de caractères dans une chaîne:

int str_len(const char* s) {
    int i = 0;
    while(*(s++)) {
        i++;
    }
    return i;
}

Pourquoi cela renvoie-t-il la bonne longueur?

Disons que j'appelle cette fonction avec une chaîne simple "a". Puis sest incrémenté dans la boucle while, donc la valeur de set isont tous les deux 0.

lor
la source

Réponses:

10

La valeur de s++est la valeur d'origine de s, avant incrément, l'incrément se produit à un moment non spécifié avant le point de séquence suivant.

D'où *s++et *(s++)sont équivalents: ils déréférencent tous les deux la valeur d'origine de s. Une autre expression équivalente est *(0, s++)et, pas pour les faibles de cœur, telle est celle-ci:0[s++]

Notez cependant que votre fonction doit utiliser type size_tfor iet son type de retour:

size_t str_len(const char *s) {
    size_t i = 0;
    while (*s++) {
        i++;
    }
    /* s points after the null terminator */
    return i;
}

Voici une version potentiellement plus efficace avec un seul incrément par boucle:

size_t str_len(const char *s) {
    const char *s0 = s;
    while (*s++) {
        /* nothing */
    }
    return s - 1 - s0;
}

Pour ceux qui s'interrogent sur les expressions étranges du deuxième paragraphe:

  • 0, s++est une instance de l'opérateur virgule ,qui évalue sa partie gauche, puis sa partie droite qui constitue sa valeur. (0, s++)est donc équivalent à (s++).

  • 0[s++]est équivalent à (s++)[0]et *(0 + s++)ou *(s++ + 0)qui se simplifie comme *(s++). La transposition du pointeur et des expressions d'index dans les []expressions n'est pas très courante ni particulièrement utile mais est conforme à la norme C.

chqrlie
la source
J'espère bien que l'opérateur virgule était clair. Enlevez les , s++choses et les mauvaises choses arriveront:)
David C. Rankin
6

Disons que j'appelle cette fonction avec une simple chaîne "a". Alors s est incrémenté dans la boucle while donc la valeur de s est 0 et i est également 0.

Dans cet exemple, les spoints à l' 'a'en "a". Ensuite, il est incrémenté et iest également incrémenté. Pointez maintenant ssur le terminateur nul, et ic'est 1. Ainsi, lors de la prochaine exécution de la boucle, *(s++)est '\0'(qui est 0), donc la boucle se termine et la valeur actuelle de i(c'est 1) est retournée.

Généralement, la boucle s'exécute une fois pour chaque caractère de la chaîne, puis s'arrête au terminateur nul, c'est ainsi que cela compte les caractères.

Flamber
la source
Parce que s est entre crochets, j'ai pensé qu'il serait incrémenté en premier (alors maintenant il pointe vers '/ 0'). Par conséquent, la boucle while est fausse et i n'est jamais incrémentée.
Lor
2
@lor, rappelez - vous ce que les opérateurs de post - incrémentation: il évalue à tout ce qui a seu lieu avant incrémenter. Ce que vous décrivez est le comportement de ++s(qui sous-compterait en effet un et invoquerait UB si une chaîne vide lui était transmise).
Toby Speight
2

Cela est parfaitement logique:

int str_len(const char* s) {
    int i = 0;
    while(*(s++)) { //<-- increments the pointer to char till the end of the string
                    //till it finds '\0', that is, if s = "a" then s is 'a'
                    // followed by '\0' so it increments one time
        i++; //counts the number of times the pointer moves forward
    }
    return i;
}

"Mais sest entre parenthèses. C'est pourquoi j'ai pensé qu'il serait incrémenté en premier"

C'est exactement pourquoi le pointeur est incrémenté et non le caractère, disons que vous l'avez (*s)++, dans ce cas, le caractère sera incrémenté et non le pointeur. Le déréférencement signifie que vous travaillez maintenant avec la valeur référencée par le pointeur, pas le pointeur lui-même.

Étant donné que les deux opérateurs ont la même priorité mais l'associativité de droite à gauche, vous pouvez même utiliser simplement *s++sans parenthèses pour incrémenter le pointeur.

anastaciu
la source
Mais s est entre parenthèses. C'est pourquoi je pensais que ce serait incrémenté en premier. (Si nous avons une chaîne simple comme "a", elle pointe désormais sur "/ 0"). Étant donné que la condition est maintenant while (0), la boucle while n'est jamais entrée.
Lor
2

L'opérateur de post-incrémentation augmente la valeur de l'opérande de 1 mais la valeur de l'expression est la valeur d'origine de l'opérande avant l'opération d'incrémentation.

Supposons que l'argument transmis à str_len()est "a". Dans le str_len(), le pointeur spointe vers le premier caractère de la chaîne "a". Dans la whileboucle:

while(*(s++)) {
.....
.....

bien que le ssera incrémenté mais la valeur de sdans l' expression sera le pointeur sur le caractère vers lequel il pointe avant l'incrémentation, qui est le pointeur sur le premier caractère 'a'. Lorsque le pointeur sest déréférencé, il donnera du caractère 'a'. Dans la prochaine itération, le spointeur pointera vers le caractère suivant qui est le caractère nul \0. Quand sest déréférencé, il donnera 0et la boucle sortira. Notez que, spointe désormais sur un élément après le caractère nul de la chaîne "a".

HS
la source