strcmp pour la chaîne vide
Je révisais du code et j'ai vu quelqu'un faire un
if (0 == strcmp(foo,""))
Je suis curieux parce que je pense qu'il serait plus rapide de faire un
if (foo[0] == '')
Est-ce correct ou strcmp est-il suffisamment optimisé pour les rendre identiques.
(je me rends compte que même s'il y avait une différence, ce serait petit, mais je pense que vous économisez au moins quelques instructions en utilisant ma méthode.)
8 réponses
Vous avez raison: puisque l'appel à strcmp()
ajoute la gestion de la pile et le saut de mémoire aux instructions strcmp réelles, vous gagnerez quelques instructions en vérifiant simplement le premier octet de votre chaîne.
Pour votre curiosité, vous pouvez vérifier le code strcmp () ici: http://sourceware.org/git/?p=glibc.git;a=blob;f=string/strcmp.c;h=bd53c05c6e21130b091bd75c3fc93872dd71fe4b;hb=HEAD
(je pensais que le code serait rempli de #ifdef
et obscur __GNUSOMETHING
, mais c'est en fait plutôt simple!)
Strcmp () est un appel de fonction et a donc une surcharge d'appel de fonction. foo [0] est un accès direct au tableau, donc c'est évidemment plus rapide.
Je ne vois aucun avantage à utiliser strcmp dans ce cas. Le compilateur est assez intelligent pour l'optimiser mais il ne sera pas plus rapide que de vérifier directement l'octet '\0'. L'implémenteur de ceci aurait pu choisir cette construction parce qu'il pensait qu'elle était plus lisible mais je pense que c'est une question de goût dans ce cas. Bien que j'écrirais le chèque un peu différent car c'est l'idiome qui semble être utilisé le plus souvent pour vérifier une chaîne vide:
if( !*str )
Et
if( *str )
Pour vérifier une chaîne non vide.
+ 1 à Gui13 pour fournir un lien vers la source de gcc stdlib strcmp (http://sourceware.org/git/?p=glibc.git;a=blob;f=string/strcmp.c;h=bd53c05c6e21130b091bd75c3fc93872dd71fe4b;hb=HEAD)!
Vous avez raison de dire que strcmp ne peut jamais être plus rapide qu'une comparaison directe[1], mais la question Est: le compilateur l'optimisera-t-il? J'ai été intimidé d'essayer de mesurer cela, mais j'ai été agréablement surpris de voir à quel point c'était facile. Mon exemple de code est (en omettant les en-têtes):
bool isEmpty(char * str) {
return 0==std::strcmp(str,"");
}
bool isEmpty2(char * str) {
return str[0]==0;
}
Et j'ai essayé compiler cela, d'abord avec gcc -S -o- emptystrcmptest.cc
et ensuite avec gcc -S -O2 -o- emptystrcmptest.cc
. À mon agréable surprise, bien que je ne puisse pas très bien lire l'assemblage, la version non optimisée a clairement montré une différence, et la version optimisée a clairement montré que les deux fonctions produisaient un assemblage identique.
Donc, je dirais qu'en général, il ne sert à rien de s'inquiéter de ce niveau d'optimisation.
Si vous utilisez un compilateur pour un système embarqué et que vous le savez pour ne pas gérer ce genre d'optimisation simple (ou n'avez pas du tout de bibliothèque standard), utilisez la version spéciale codée à la main.
Si vous codez normalement, utilisez la version la plus lisible (à mon humble avis qui peut être strcmp ou strlen ou [0] = = 0 selon le contexte).
Si vous écrivez du code très efficace, vous vous attendez à être appelé des milliers ou des millions de fois par seconde, (a) test qui est en fait plus efficace et (b) si la version lisible est en fait trop lente, essayez d'écrire somethign qui compilera pour mieux Assemblée.
Avec gcc -S -o- emptystrcmptest.cc
:
.file "emptystrcmptest.cc"
.section .rdata,"dr"
LC0:
.ascii "\0"
.text
.align 2
.globl __Z7isEmptyPc
.def __Z7isEmptyPc; .scl 2; .type 32; .endef
__Z7isEmptyPc:
pushl %ebp
movl %esp, %ebp
subl $24, %esp
movl $LC0, 4(%esp)
movl 8(%ebp), %eax
movl %eax, (%esp)
call _strcmp
movl %eax, -4(%ebp)
cmpl $0, -4(%ebp)
sete %al
movzbl %al, %eax
movl %eax, -4(%ebp)
movl -4(%ebp), %eax
leave
ret
.align 2
.globl __Z8isEmpty2Pc
.def __Z8isEmpty2Pc; .scl 2; .type 32; .endef
__Z8isEmpty2Pc:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax
cmpb $0, (%eax)
sete %al
movzbl %al, %eax
popl %ebp
ret
emptystrcmptest.cc:10:2: warning: no newline at end of file
.def _strcmp; .scl 2; .type 32; .endef
Avec gcc -S -O2 -o- emptystrcmptest.cc
:
.file "emptystrcmptest.cc"
emptystrcmptest.cc:10:2: warning: no newline at end of file
.text
.align 2
.p2align 4,,15
.globl __Z7isEmptyPc
.def __Z7isEmptyPc; .scl 2; .type 32; .endef
__Z7isEmptyPc:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax
popl %ebp
cmpb $0, (%eax)
sete %al
movzbl %al, %eax
ret
.align 2
.p2align 4,,15
.globl __Z8isEmpty2Pc
.def __Z8isEmpty2Pc; .scl 2; .type 32; .endef
__Z8isEmpty2Pc:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax
popl %ebp
cmpb $0, (%eax)
sete %al
movzbl %al, %eax
ret
[1] bien que soyez prudent-dans les cas plus compliqués qu'un test direct contre zéro, le code de la bibliothèque et du compilateur a sera généralement meilleur que le code artisanal.
Un bon compilateur d'optimisation peut optimiser l'appel de fonction, puis éliminer la boucle de la fonction inline. Il n'y a aucun moyen que votre méthode puisse être plus lente, bien qu'il y ait une chance que ce soit la même vitesse.
Accès à un tableau est d'ordre 1 en temps d'exécution, donc, son plus rapide que la fonction.
C'est aussi micro-optimisation que possible, mais je suppose que si vous avez ajouté une vérification null avant d'indexer foo (sauf si vous savez que ce ne sera jamais null), cela économiserait techniquement la surcharge d'un appel de fonction.
Cela va clairement être plus rapide, et il vaut probablement la peine de placer votre propre code dans une fonction en ligne, ou peut-être même une macro, si vous prévoyez d'aller de l'avant avec:
int isEmpty(const char *string)
{
return ! *string;
}
int isNotEmpty(const char *string)
{
return *string;
}
int isNullOrEmpty(const char *string)
{
return string == NULL || ! *string;
}
int isNotNullOrEmpty(const char *string)
{
return string != NULL && *string;
}
Et laissez le compilateur optimiser cela pour vous. Quoi qu'il en soit, strcmp
doit éventuellement vérifier '\0'
de sorte que vous êtes toujours au moins égal à cela. (honnêtement, je laisserais probablement le compilateur optimiser le partage interne de ce qui précède, par exemple, {[3] } serait probablement juste flip isNotEmpty
)