strcpy() valeur de retour

une grande partie des fonctions de la bibliothèque standard C, en particulier celles pour la manipulation des chaînes, et plus particulièrement strcpy(), partagent le prototype suivant:

char *the_function (char *destination, ...)

La valeur de retour de ces fonctions est en fait le même que le destination . Pourquoi gaspiller la valeur de retour pour quelque chose de redondant? Il est plus logique pour une telle fonction nulle ou retour quelque chose d'utile.

est - ce qu'il est plus facile et plus pratique d'insérer l'appel de fonction dans une autre expression, par exemple:

printf("%sn", strcpy(dst, src));

y a-t-il d'autres raisons sensées pour justifier cet idiome?

24
demandé sur Blagovest Buyukliev 2010-08-25 02:07:55

6 réponses

comme Evan l'a souligné, il est possible de faire quelque chose comme

char* s = strcpy(malloc(10), "test");

p.ex. assigner malloc()ed mémoire une valeur, sans utiliser la variable helper.

(cet exemple n'est pas le meilleur, il va se planter sur la mémoire de conditions, mais l'idée est évident)

19
répondu nothrow 2010-08-24 22:16:13

je crois que votre supposition est correcte, il est plus facile de nicher l'appel.

5
répondu Evan Teran 2010-08-24 22:10:20

C'est aussi extrêmement facile à coder.

la valeur de retour est typiquement laissée dans le registre AX (ce n'est pas obligatoire, mais c'est souvent le cas). Et la destination est mise dans le registre de la hache lorsque la fonction démarre. Pour retourner la destination, le programmeur doit le faire.... exactement rien! Il suffit de laisser la valeur là où il est.

Le programmeur pourrait déclarer la fonction void . Mais cette valeur de retour est déjà dans le droit spot, juste en attendant d'être rendu, et il ne coûte même pas une instruction supplémentaire pour le rendre! Peu importe la petite amélioration, il est pratique dans certains cas.

1
répondu abelenky 2010-08-24 22:21:02

char *stpcpy(char *dest, const char *src); renvoie un pointeur à la fin de la chaîne, et fait partie de POSIX.1-2008 date . Avant cela, C'était une extension GNU libc depuis 1992. Si apparu pour la première fois dans le réseau AmigaDOS en 1986.

gcc -O3 optimisera dans certains cas strcpy + strcat pour utiliser stpcpy ou strlen + copie en ligne, Voir ci-dessous.


La bibliothèque standard de

C a été conçue très tôt, et il est très facile de soutenir que les fonctions str* ne sont pas optimisées. Les fonctions D'entrée / sortie ont été définitivement conçu très tôt, en 1972 avant que C même eu un préprocesseur, qui est pourquoi fopen(3) prend une chaîne de mode au lieu d'une bitmap drapeau comme Unix open(2) .

Je n'ai pas été en mesure de trouver une liste des fonctions incluses dans "portable I/O package" de Mike Lesk, donc je ne sais pas si strcpy dans sa forme actuelle date tout le chemin de retour à là ou si ces fonctions ont été ajoutées plus tard. (La seule vraie source que j'ai trouvé est Dennis Ritchie est largement connue Histoire C de l'article , qui est excellente, mais pas que en profondeur. Je n'ai pas trouvé de documentation ou de code source pour le paquet D'E/S lui-même.)

Ils apparaissent dans leur la forme en K&R de la première édition , 1978.


les fonctions devraient retourner le résultat du calcul qu'elles font, si c'est potentiellement utile à l'appelant, au lieu de le jeter . Soit en pointeur vers la fin de la chaîne, soit en longueur entière. (Un pointeur serait naturel.)

@R dit:

Nous souhaitons tous ces fonctions renvoie un pointeur vers le octet null terminal (ce qui réduirait beaucoup d'opérations O(n) à O(1) )

par exemple appeler strcat(bigstr, newstr[i]) dans une boucle pour construire une longue chaîne à partir de nombreuses chaînes courtes (O(1) length) a environ O(n^2) complexité, mais strlen / memcpy ne regardera chaque caractère que deux fois (une fois dans strlen, une fois dans memcpy).

en utilisant seulement la bibliothèque standard ANSI C, il n'y a pas façon de regarder efficacement seulement à chaque caractère une fois . Vous pouvez écrire manuellement une boucle byte-at-a-time, mais pour des chaînes plus longues que quelques octets, c'est pire que de regarder chaque caractère deux fois avec les compilateurs actuels (qui ne vont pas auto-vectoriser une boucle de recherche) sur HW moderne, étant donné efficace libc fourni SIMD strlen et memcpy. Vous pouvez utiliser length = sprintf(bigstr, "%s", newstr[i]); bigstr+=length; , mais sprintf() doit analyser sa chaîne de format et est pas rapide.

il n'y a même pas une version de strcmp ou memcmp qui renvoie la position de la différence . Si c'est ce que vous voulez, vous avez le même problème que pourquoi la comparaison des chaînes est-elle si rapide en python? : une fonction de bibliothèque optimisée qui s'exécute plus rapidement que tout ce que vous pouvez faire avec une boucle compilée (à moins que vous n'ayez un asm optimisé à la main pour chaque plate-forme cible dont vous vous souciez), que vous pouvez utiliser pour rapprochez-vous de l'octet différent avant de retomber dans une boucle régulière une fois que vous êtes proche.

il semble que la bibliothèque à cordes de C a été conçue sans tenir compte du coût O(n) de n'importe quelle opération, non seulement trouver la fin des chaînes de longueur implicite, et strcpy le comportement de" n'est certainement pas le seul exemple.

ils traitent essentiellement les cordes de longueur implicite comme des objets opaques entiers, renvoyant toujours des pointeurs à la démarrer, jamais à la fin ou à une position à l'intérieur d'un après recherche, ou l'ajout d'.


Histoire des conjectures

Au début de C sur un PDP-11 , je soupçonne que strcpy n'était pas plus efficace que while(*dst++ = *src++) {} (et a probablement été mis en œuvre en ce sens).

En fait, K&R de la première édition (page 101) montre que la mise en œuvre de strcpy et dit:

bien que cela puisse sembler énigmatique à première vue, la commodité de la notation est considérable, et l'idiome devrait être maîtrisé, si pour aucune autre raison que celle que vous verrez fréquemment dans les programmes C.

cela implique ils s'attendaient pleinement à ce que les programmeurs écrivent leurs propres boucles dans les cas où vous vouliez la valeur finale de dst ou src . Et donc peut-être qu'ils Je ne voyais pas la nécessité de reconcevoir l'API de bibliothèque standard jusqu'à ce qu'il soit trop tard pour exposer plus d'API utiles pour les fonctions de bibliothèque ASM optimisées à la main.


mais est-ce que retourner la valeur originale de dst a un sens?

strcpy(dst, src) retour "à la 1519250920" est analogue à la x=y l'évaluation de la x . Ainsi, strcpy fonctionne comme un opérateur de chaîne de caractères.

comme d'autres réponses le soulignent, cela permet la nidification, comme foo( strcpy(buf,input) ); . Les premiers ordinateurs étaient très limités en mémoire. garder votre code source compact était une pratique courante . Les cartes perforées et les terminaux lents ont probablement joué un rôle à cet égard. Je ne connais pas les normes historiques de codage ou les guides de style ou ce qui était considéré comme trop important pour être mis en ligne.

Vieux compilateurs croustillants étaient peut-être aussi un facteur. Avec moderne optimiser les compilateurs, char *tmp = foo(); / bar(tmp); n'est pas plus lent que bar(foo()); , mais c'est avec gcc -O0 . Je ne sais pas si les premiers compilateurs pourraient optimiser complètement les variables (en ne leur réservant pas d'espace sur la pile), mais j'espère qu'ils pourront au moins les conserver dans des registres dans des cas simples (contrairement à l'actuel gcc -O0 qui renverse/recharge volontairement tout pour un débogage cohérent). c'est à dire gcc -O0 n'est pas un bon modèle pour les anciens compilateurs, parce que c'est anti-optimisation sur le but cohérent de débogage.


motivation possible générée par le compilateur-asm

étant donné le manque d'attention à l'efficacité dans la conception générale de L'API de la bibliothèque C string, cela pourrait être peu probable. Mais peut-être qu'il y avait un avantage de taille code. (Sur les premiers ordinateurs, la taille du code était plus une limite dure que le temps CPU).

Je ne sais pas grand chose sur la qualité de premiers compilateurs C, mais il y a fort à parier qu'ils n'étaient pas géniaux pour optimiser, même pour une architecture simple / orthogonale comme PDP-11.

il est courant de vouloir le pointeur de chaîne après l'appel de fonction. À l'asm niveau, vous (le compilateur) a probablement dans un registre avant l'appel. Selon convention d'appel, soit vous le poussez sur la pile ou de la copier vers la droite registre où la convention d'appel déclare le premier arg aller. (c'est à dire la strcpy attend). Ou si vous planifiez à l'avance, vous aviez déjà le pointeur dans le bon Registre pour la Convention d'appel.

mais la fonction appelle clobber certains registres, y compris tous les registres arg-passing. (Ainsi, lorsqu'une fonction obtient un arg dans un registre, elle peut l'incrémenter là au lieu de copier dans un registre à gratter.)

ainsi que l'appelant, votre option code-gen pour garder quelque chose à travers une fonction appel inclure:

  • stocker/recharger dans la mémoire de pile locale. (Ou tout simplement recharger si une copie à jour est toujours en mémoire).
  • enregistrez / restaurez un registre de conservation d'appel au début/à la fin de toute votre fonction, et copiez le pointeur sur l'un de ces registres avant l'appel de fonction.
  • la fonction renvoie la valeur dans un registre pour vous. (Bien sûr, cela ne fonctionne que si la source C est écrite pour utiliser le retourner la valeur à la place de de la variable d'entrée. par exemple dst = strcpy(dst, src); si vous ne nichez pas).

toutes les conventions d'appel sur toutes les architectures je suis au courant des valeurs de retour de la taille d'un pointeur de retour dans un registre, donc avoir peut-être une instruction supplémentaire dans la fonction de bibliothèque peut sauver la taille de code dans tous les appelants qui veulent utiliser cette valeur de retour.

vous avez probablement obtenu de meilleurs asm de compilateurs primitifs de début C par en utilisant la valeur de retour de strcpy (déjà dans un registre) qu'en faisant le compilateur enregistrer le pointeur autour de l'appel dans un registre d'appel préservé ou le déverser sur la pile. Ce peut être encore le cas.

BTW, sur de nombreux Isa, le registre de valeur de retour n'est pas le premier registre arg-passing. Et à moins que vous n'utilisiez les modes d'adressage base+index, il vous en coûtera une instruction supplémentaire (et attacher une autre reg) pour que strcpy copie le Registre pour une boucle pointeur-incrément.

PDP-11 chaînes d'outils normalement utilisé une sorte de stack-args appelant la convention , toujours en poussant args sur la pile. Je ne sais pas combien de registres à mémorisation et à mémorisation d'appels étaient normaux, mais seulement 5 ou 6 règles GP étaient disponibles ( R7 étant le compteur de programmes, R6 étant le pointeur de pile, R5 souvent utilisé comme pointeur de trame ). Donc c'est similaire à mais encore plus à l'étroit que 32-bit x86.

char *bar(char *dst, const char *str1, const char *str2)
{
    //return strcat(strcat(strcpy(dst, str1), "separator"), str2);

    // more readable to modern eyes:
    dst = strcpy(dst, str1);
    dst = strcat(dst, "separator");
//    dst = strcat(dst, str2);

    return dst;  // simulates further use of dst
}

  # x86 32-bit gcc output, optimized for size (not speed)
  # gcc8.1 -Os  -fverbose-asm -m32
  # input args are on the stack, above the return address

    push    ebp     #
    mov     ebp, esp  #,      Create a stack frame.

    sub     esp, 16   #,      This looks like a missed optimization, wasted insn
    push    DWORD PTR [ebp+12]      # str1
    push    DWORD PTR [ebp+8]       # dst
    call    strcpy  #
    add     esp, 16   #,

    mov     DWORD PTR [ebp+12], OFFSET FLAT:.LC0      # store new args over our incoming args
    mov     DWORD PTR [ebp+8], eax    #  EAX = dst.
    leave   
    jmp     strcat                  # optimized tailcall of the last strcat

c'est nettement plus compact qu'une version qui n'utilise pas dst = , et réutilise plutôt l'arg d'entrée pour le strcat . (Voir à la fois sur le compilateur Godbolt explorer .)

la sortie -O3 est très différente: gcc pour la version qui n'utilise pas la valeur de retour utilise stpcpy (renvoie un pointeur vers la queue) et puis mov - immédiat pour stocker le littéral string data directement au bon endroit.

mais malheureusement, la version dst = strcpy(dst, src) - O3 utilise toujours régulier strcpy , puis inlines strcat comme strlen + mov - immédiat.


à C-string ou non

C les chaînes à longueur implicite ne sont pas toujours intrinsèquement mauvais, et ont des avantages intéressants (par exemple, un suffixe est également valide chaîne, sans avoir à le copier).

mais la bibliothèque C string n'est pas conçue d'une manière qui rend le code efficace possible, parce que char - at-a-time loops ne s'auto-vectorisent généralement pas et les fonctions de la bibliothèque jettent les résultats du travail qu'ils ont à faire.

gcc et clang ne font jamais de boucles auto-vectorize à moins que le nombre d'itérations ne soit connu avant la première itération, p.ex. for(int i=0; i<n ;i++) . ICC peut vectoriser les boucles de recherche, mais il est encore peu probable de faire aussi bien qu'un asm écrit à la main.


strncpy et ainsi de suite sont essentiellement un désastre . par exemple strncpy ne copie pas la terminaison '" 1519560920 "' si elle atteint la limite de taille du tampon. Il semble avoir été conçu pour écrire au milieu de cordes plus grandes, pas pour éviter les débordements de tampon. Ne pas retourner un pointeur à la fin signifie que vous devez arr[n] = 0; avant ou après, touchant potentiellement une page de mémoire qui n'a jamais eu besoin d'être touchée.

quelques fonctions comme snprintf sont utilisables et font toujours nul-terminate. Se souvenir de qui fait ce qui est difficile, et un risque énorme si vous vous rappelez mal, donc vous devez vérifier à chaque fois dans les cas où il importe de l'exactitude.

comme Bruce Dawson dit: arrêtez d'utiliser strncpy déjà! . Apparemment, certaines extensions MSVC comme _snprintf sont encore pires.

1
répondu Peter Cordes 2018-07-26 21:15:21

même concept que Interfaces fluentes . Je rends juste le code plus rapide / plus facile à lire.

0
répondu palswim 2010-08-24 22:22:00

Je ne pense pas que ce soit vraiment réglé de cette façon pour les besoins de la nidification, mais plus pour la vérification des erreurs. Si la mémoire ne sert aucune des fonctions de la bibliothèque c standard font beaucoup de vérification d'erreur sur leur propre et il est donc plus logique que ce serait pour déterminer si quelque chose est allé de Travers pendant l'appel strcpy.

if(strcpy(dest, source) == NULL) {
  // Something went horribly wrong, now we deal with it
}
0
répondu Schoktra 2016-02-29 13:34:02