Vou supor que sem uma otimização sofisticada que possa detectar que 'ss' nunca muda no loop, então sim. Melhor compilar e olhar para a montagem para ver.
MerickOWA
6
Depende do compilador, do nível de otimização e do que você (pode) fazer para ssdentro do loop.
Hristo Iliev
4
Se o compilador puder provar que ssnunca é modificado, ele pode içar o cálculo para fora do loop.
Daniel Fischer
10
@ Mike: "requer análise em tempo de compilação de exatamente o que strlen faz" - strlen é provavelmente um intrínseco, caso em que o otimizador sabe o que faz.
Steve Jessop
3
@MikeSeymour: Não existe talvez, talvez não. strlen é definido pelo padrão da linguagem C e seu nome é reservado para o uso definido pela linguagem, de modo que um programa não está livre para fornecer uma definição diferente. O compilador e o otimizador têm o direito de assumir que strlen depende unicamente de sua entrada e não o modifica ou qualquer estado global. O desafio da otimização aqui é determinar que a memória apontada por ss não é alterada por nenhum código dentro do loop. Isso é totalmente viável com os compiladores atuais, dependendo do código específico.
Eric Postpischil
Respostas:
138
Sim, strlen()será avaliado em cada iteração. É possível que, em circunstâncias ideais, o otimizador consiga deduzir que o valor não mudará, mas eu pessoalmente não confiaria nisso.
Eu faria algo como
for(int i =0, n = strlen(ss); i < n;++i)
ou possivelmente
for(int i =0; ss[i];++i)
contanto que a string não mude de comprimento durante a iteração. Se for o caso, você precisará ligar a strlen()cada vez ou lidar com isso por meio de uma lógica mais complicada.
Se você sabe que não está manipulando a string, o segundo é muito mais preferível, pois é essencialmente o loop que será executado de strlenqualquer maneira.
mlibby
26
@alk: Se a string pode ficar encurtada, então ambos estão errados.
Mike Seymour
3
@alk: se você estiver mudando a string, um loop for provavelmente não é a melhor maneira de iterar cada caractere. Eu acho que um loop while é mais direto e fácil de gerenciar o contador de índice.
mlibby
2
circunstâncias ideais incluem compilar com GCC no Linux, onde strlené marcado como __attribute__((pure))permitindo que o compilador elide várias chamadas. Atributos GCC
David Rodríguez - dribeas
6
A segunda versão é a forma ideal e mais idiomática. Ele permite que você passe a string apenas uma vez em vez de duas, o que terá um desempenho muito melhor (especialmente a coerência do cache) para strings longas.
R .. GitHub PARAR DE AJUDAR O ICE
14
Sim, toda vez que você usa o loop. Então, sempre calculará o comprimento da corda. então use-o assim:
char str[30];for(int i =0; str[i]!='\0'; i++){//Something;}
No código acima, str[i]verifica apenas um caractere específico na string no local icada vez que o loop inicia um ciclo, portanto, ele ocupará menos memória e será mais eficiente.
No código abaixo, toda vez que o loop for executado strlen, o comprimento de toda a string será contado, o que é menos eficiente, leva mais tempo e ocupa mais memória.
char str[];for(int i =0; i < strlen(str); i++){//Something;}
Posso concordar com "[é] mais eficiente", mas usa menos memória? A única diferença de uso de memória que eu consigo pensar seria na pilha de chamadas durante a strlenchamada, e se você está correndo tão apertado, provavelmente deveria estar pensando em eliminar algumas outras chamadas de função também ...
um CVn
@ MichaelKjörling Bem, se você usar "strlen", então em um loop ele terá que verificar toda a string toda vez que o loop for executado, enquanto no código acima o "str [ix]", ele verifica apenas um elemento durante cada ciclo do loop cuja localização é representada por "ix". Assim, leva menos memória do que "strlen".
códigoDEXTER
1
Não tenho certeza se isso faz muito sentido, na verdade. Uma implementação muito ingênua de strlen seria algo como int strlen(char *s) { int len = 0; while(s[len] != '\0') len++; return len; }que é exatamente o que você está fazendo no código em sua resposta. Não estou argumentando que iterar na string uma vez em vez de duas é mais eficiente em termos de tempo , mas não vejo um ou outro usando mais ou menos memória. Ou você está se referindo à variável usada para manter o comprimento da corda?
um CVn de
@ MichaelKjörling Por favor, veja o código editado acima e o link. E quanto à memória - toda vez que o loop é executado, cada valor que itera é armazenado na memória e, no caso de 'strlen', uma vez que conta toda a string repetidamente, requer mais memória para armazenar. e também porque, ao contrário do Java, C ++ não possui "Garbage Collector". Então eu posso estar errado também. veja o link sobre a ausência de "Coletor de lixo" em C ++.
códigoDEXTER
1
@ aashis2s A falta de um coletor de lixo apenas desempenha um papel ao criar objetos no heap. Os objetos na pilha são destruídos assim que o escopo e termina.
Ikke
9
Um bom compilador pode não calculá-lo sempre, mas não acho que você possa ter certeza de que todo compilador faz isso.
Além disso, o compilador tem que saber, isso strlen(ss)não muda. Isso só é verdadeiro se ssnão for alterado no forloop.
Por exemplo, se você usar uma função somente leitura ssno forloop, mas não declarar o ssparâmetro-como const, o compilador nem poderá saber que ssnão foi alterado no loop e terá que calcular a strlen(ss)cada iteração.
+1: Não só ssnão deve ser alterado no forloop; ele não deve ser acessível e alterado por qualquer função chamada no loop (porque é passado como um argumento, ou porque é uma variável global ou uma variável de escopo de arquivo). A qualificação constante também pode ser um fator.
Jonathan Leffler
4
Eu acho altamente improvável que o compilador saiba que 'ss' não muda. Pode haver ponteiros perdidos que apontam para a memória dentro de 'ss', os quais o compilador não tem idéia e que podem mudar 'ss'
MerickOWA
Jonathan está certo, uma string const local pode ser a única maneira de o compilador ter certeza de que não há como 'ss' mudar.
MerickOWA
2
@MerickOWA: na verdade, essa é uma das coisas que restrictvale a pena no C99.
Steve Jessop
4
Em relação ao seu último para: se você chamar uma função somente leitura ssno loop for, mesmo que seu parâmetro seja declarado const char*, o compilador ainda precisa recalcular o comprimento, a menos que (a) saiba que ssaponta para um objeto const, ao invés de ser apenas um ponteiro para const, ou (b) pode incorporar a função ou ver que é somente leitura. Tomar um const char*parâmetro não é uma promessa de não modificar os dados apontados, porque é válido lançar char*e modificar, desde que o objeto modificado não seja const e não seja um literal de string.
Steve Jessop
4
Se ssé do tipo const char *e você não está descartando o constness dentro do loop, o compilador pode chamar apenas strlenuma vez, se as otimizações estiverem ativadas. Mas certamente não é um comportamento com o qual se possa confiar.
Você deve salvar o strlenresultado em uma variável e usar essa variável no loop. Se você não quiser criar uma variável adicional, dependendo do que estiver fazendo, pode ser que você não se importe com a reversão do loop para iterar para trás.
for(auto i = strlen(s); i >0;--i ){// do whatever// remember value of s[strlen(s)] is the terminating NULL character}
É um erro telefonar strlen. Apenas faça um loop até chegar ao fim.
R .. GitHub PARAR DE AJUDAR O ICE
i > 0? Isso não deveria estar i >= 0aqui? Pessoalmente, eu também começaria strlen(s) - 1se iterando a string de trás para frente, a terminação \0não precisa de consideração especial.
um CVn de
2
@ MichaelKjörling i >= 0funciona apenas se você inicializar com strlen(s) - 1, mas então se você tiver uma string de comprimento zero, o valor inicial underflows
Praetorian
@ Prætorian, bom ponto na string de comprimento zero. Não considerei esse caso quando escrevi meu comentário. O C ++ avalia a i > 0expressão na entrada inicial do loop? Se não, então você está certo, o caso de comprimento zero definitivamente quebrará o loop. Em caso afirmativo, você "simplesmente" obtém um sinal i== -1 <0, portanto, nenhuma entrada de loop se a condicional for i >= 0.
um CVn de
@ MichaelKjörling Sim, a condição de saída é avaliada antes de executar o loop pela primeira vez. strleno tipo de retorno de não tem sinal, portanto, é (strlen(s)-1) >= 0avaliado como verdadeiro para strings de comprimento zero.
Pretoriano
3
Formalmente sim, strlen()espera-se que seja chamado a cada iteração.
De qualquer forma, não quero negar a possibilidade da existência de alguma otimização inteligente do compilador, que otimizará qualquer chamada sucessiva a strlen () após a primeira.
O código do predicado em sua totalidade será executado em cada iteração do forloop. Para memorizar o resultado da strlen(ss)chamada, o compilador precisa saber que pelo menos
A função não strlentinha efeitos colaterais
A memória apontada por ssnão muda durante o loop
O compilador não sabe nenhuma dessas coisas e, portanto, não pode memorizar com segurança o resultado da primeira chamada
Bem, ele poderia saber essas coisas com a análise estática, mas acho que seu ponto é que essa análise atualmente não está implementada em nenhum compilador C ++, certo?
GManNickG
@GManNickG definitivamente poderia ser o número 1, mas o número 2 é mais difícil. Para um único thread, sim, poderia definitivamente provar, mas não para um ambiente multi-thread.
JaredPar
1
Talvez eu esteja sendo teimoso, mas acho que o número dois também é possível em ambientes multithread, mas definitivamente não sem um sistema de inferência extremamente forte. Apenas meditando aqui; definitivamente além do escopo de qualquer compilador C ++ atual.
GManNickG
@GManNickG Eu não acho que seja possível em C / C ++. Eu poderia facilmente esconder o endereço de ssem um size_tou dividi-lo entre vários bytevalores. Meu segmento tortuoso poderia então simplesmente escrever bytes naquele endereço e o compilador saberia uma maneira de entender a que se relaciona ss.
JaredPar
1
@JaredPar: Desculpe insistir, você pode alegar que isso int a = 0; do_something(); printf("%d",a);não pode ser otimizado, com base no que do_something()pode fazer sua coisa interna não inicializada ou pode rastejar de volta para a pilha e modificar adeliberadamente. Na verdade, o gcc 4.5 o otimiza para do_something(); printf("%d",0);com -O3
Steve Jessop
2
Sim . strlen será calculado sempre que i aumentar.
Se você não alterou ss com no loop , isso não afetará a lógica, caso contrário, afetará.
É mais seguro usar o código a seguir.
int length = strlen(ss);for(int i =0; i < length ;++ i ){// blabla}
Sim, o strlen(ss)irá calcular o comprimento em cada iteração. Se você estiver aumentando o ssde alguma forma e também aumentando o i; haveria um loop infinito.
SIM, em palavras simples. E existe um pequeno não em raras condições em que o compilador deseja, como uma etapa de otimização, se ele descobrir que não há nenhuma alteração feita ss. Mas em condições seguras, você deve pensar que sim. Existem algumas situações como em multithreadedum programa orientado a eventos, ele pode apresentar erros se você considerar um NÃO. Jogue com segurança, pois isso não vai melhorar muito a complexidade do programa.
Acho que fazer essa modificação em particular nunca altera o comprimento de ss, apenas seu conteúdo, portanto (um compilador muito, muito inteligente) ainda poderia otimizar strlen.
A condição do loop é avaliada após cada repetição, antes de reiniciar o loop.
Também tome cuidado com o tipo que você usa para lidar com o comprimento das cordas. deve ser o size_tque foi definido como unsigned intem stdio. compará-lo e convertê-lo em intpode causar alguns problemas graves de vulnerabilidade.
bem, percebi que alguém está dizendo que ele é otimizado por padrão por qualquer compilador moderno "inteligente". A propósito, veja os resultados sem otimização. Eu tentei: Código C mínimo:
Meu compilador: g ++ (Ubuntu / Linaro 4.6.3-1ubuntu5) 4.6.3
Comando para geração de código de montagem: g ++ -S -masm = intel test.cpp
Gotten assembly code at the output:...
L3:
mov DWORD PTR [esp],97
call putchar
add DWORD PTR [esp+40],1.L2:
THIS LOOP IS HERE
**<b>mov ebx, DWORD PTR [esp+40]
mov eax, DWORD PTR [esp+44]
mov DWORD PTR [esp+28],-1
mov edx, eax
mov eax,0
mov ecx, DWORD PTR [esp+28]
mov edi, edx
repnz scasb</b>**
AS YOU CAN SEE it's done every time
mov eax, ecx
not eax
sub eax, 1
cmp ebx, eax
setb al
test al, al
jne .L3
mov eax, 0
.....
Eu não confiaria em qualquer compilador que tentasse otimizá-lo, a menos que o endereço da string fosse restrict-qualificado. Embora existam alguns casos em que tal otimização seria legítima, o esforço necessário para identificar de forma confiável tais casos na ausência de restrict, por qualquer medida razoável, quase certamente excederia o benefício. Se o endereço da string tivesse um const restrictqualificador, entretanto, isso seria suficiente por si só para justificar a otimização sem ter que olhar para mais nada.
supercat de
0
Elaborando a resposta de Prætorian, recomendo o seguinte:
for(auto i = strlen(s)-1; i >0;--i ){foo(s[i-1];}
autoporque você não quer se preocupar com o tipo de retorno de strlen. Um compilador C ++ 11 (por exemplo gcc -std=c++0x, não completamente C ++ 11, mas os tipos automáticos funcionam) fará isso por você.
i = strlen(s)porque você deseja comparar 0(veja abaixo)
i > 0 porque a comparação com 0 é (ligeiramente) mais rápida que a comparação com qualquer outro número.
a desvantagem é que você tem que usar i-1para acessar os caracteres da string.
ss
dentro do loop.ss
nunca é modificado, ele pode içar o cálculo para fora do loop.Respostas:
Sim,
strlen()
será avaliado em cada iteração. É possível que, em circunstâncias ideais, o otimizador consiga deduzir que o valor não mudará, mas eu pessoalmente não confiaria nisso.Eu faria algo como
ou possivelmente
contanto que a string não mude de comprimento durante a iteração. Se for o caso, você precisará ligar a
strlen()
cada vez ou lidar com isso por meio de uma lógica mais complicada.fonte
strlen
qualquer maneira.strlen
é marcado como__attribute__((pure))
permitindo que o compilador elide várias chamadas. Atributos GCCSim, toda vez que você usa o loop. Então, sempre calculará o comprimento da corda. então use-o assim:
No código acima,
str[i]
verifica apenas um caractere específico na string no locali
cada vez que o loop inicia um ciclo, portanto, ele ocupará menos memória e será mais eficiente.Veja este link para mais informações.
No código abaixo, toda vez que o loop for executado
strlen
, o comprimento de toda a string será contado, o que é menos eficiente, leva mais tempo e ocupa mais memória.fonte
strlen
chamada, e se você está correndo tão apertado, provavelmente deveria estar pensando em eliminar algumas outras chamadas de função também ...int strlen(char *s) { int len = 0; while(s[len] != '\0') len++; return len; }
que é exatamente o que você está fazendo no código em sua resposta. Não estou argumentando que iterar na string uma vez em vez de duas é mais eficiente em termos de tempo , mas não vejo um ou outro usando mais ou menos memória. Ou você está se referindo à variável usada para manter o comprimento da corda?Um bom compilador pode não calculá-lo sempre, mas não acho que você possa ter certeza de que todo compilador faz isso.
Além disso, o compilador tem que saber, isso
strlen(ss)
não muda. Isso só é verdadeiro sess
não for alterado nofor
loop.Por exemplo, se você usar uma função somente leitura
ss
nofor
loop, mas não declarar oss
parâmetro-comoconst
, o compilador nem poderá saber quess
não foi alterado no loop e terá que calcular astrlen(ss)
cada iteração.fonte
ss
não deve ser alterado nofor
loop; ele não deve ser acessível e alterado por qualquer função chamada no loop (porque é passado como um argumento, ou porque é uma variável global ou uma variável de escopo de arquivo). A qualificação constante também pode ser um fator.restrict
vale a pena no C99.ss
no loop for, mesmo que seu parâmetro seja declaradoconst char*
, o compilador ainda precisa recalcular o comprimento, a menos que (a) saiba quess
aponta para um objeto const, ao invés de ser apenas um ponteiro para const, ou (b) pode incorporar a função ou ver que é somente leitura. Tomar umconst char*
parâmetro não é uma promessa de não modificar os dados apontados, porque é válido lançarchar*
e modificar, desde que o objeto modificado não seja const e não seja um literal de string.Se
ss
é do tipoconst char *
e você não está descartando oconst
ness dentro do loop, o compilador pode chamar apenasstrlen
uma vez, se as otimizações estiverem ativadas. Mas certamente não é um comportamento com o qual se possa confiar.Você deve salvar o
strlen
resultado em uma variável e usar essa variável no loop. Se você não quiser criar uma variável adicional, dependendo do que estiver fazendo, pode ser que você não se importe com a reversão do loop para iterar para trás.fonte
strlen
. Apenas faça um loop até chegar ao fim.i > 0
? Isso não deveria estari >= 0
aqui? Pessoalmente, eu também começariastrlen(s) - 1
se iterando a string de trás para frente, a terminação\0
não precisa de consideração especial.i >= 0
funciona apenas se você inicializar comstrlen(s) - 1
, mas então se você tiver uma string de comprimento zero, o valor inicial underflowsi > 0
expressão na entrada inicial do loop? Se não, então você está certo, o caso de comprimento zero definitivamente quebrará o loop. Em caso afirmativo, você "simplesmente" obtém um sinali
== -1 <0, portanto, nenhuma entrada de loop se a condicional fori >= 0
.strlen
o tipo de retorno de não tem sinal, portanto, é(strlen(s)-1) >= 0
avaliado como verdadeiro para strings de comprimento zero.Formalmente sim,
strlen()
espera-se que seja chamado a cada iteração.De qualquer forma, não quero negar a possibilidade da existência de alguma otimização inteligente do compilador, que otimizará qualquer chamada sucessiva a strlen () após a primeira.
fonte
O código do predicado em sua totalidade será executado em cada iteração do
for
loop. Para memorizar o resultado dastrlen(ss)
chamada, o compilador precisa saber que pelo menosstrlen
tinha efeitos colateraisss
não muda durante o loopO compilador não sabe nenhuma dessas coisas e, portanto, não pode memorizar com segurança o resultado da primeira chamada
fonte
ss
em umsize_t
ou dividi-lo entre váriosbyte
valores. Meu segmento tortuoso poderia então simplesmente escrever bytes naquele endereço e o compilador saberia uma maneira de entender a que se relacionass
.int a = 0; do_something(); printf("%d",a);
não pode ser otimizado, com base no quedo_something()
pode fazer sua coisa interna não inicializada ou pode rastejar de volta para a pilha e modificara
deliberadamente. Na verdade, o gcc 4.5 o otimiza parado_something(); printf("%d",0);
com -O3Sim . strlen será calculado sempre que i aumentar.
Se você não alterou ss com no loop , isso não afetará a lógica, caso contrário, afetará.
É mais seguro usar o código a seguir.
fonte
Sim, o
strlen(ss)
irá calcular o comprimento em cada iteração. Se você estiver aumentando oss
de alguma forma e também aumentando oi
; haveria um loop infinito.fonte
Sim, a
strlen()
função é chamada sempre que o loop é avaliado.Se você deseja melhorar a eficiência, lembre-se sempre de salvar tudo em variáveis locais ... Isso levará algum tempo, mas é muito útil.
Você pode usar o código abaixo:
fonte
Sim,
strlen(ss)
será calculado sempre que o código for executado.fonte
Não é comum hoje em dia, mas há 20 anos em plataformas de 16 bits, eu recomendo isso:
Mesmo que seu compilador não seja muito inteligente em otimização, o código acima pode resultar em um bom código de montagem ainda.
fonte
Sim. O teste não sabe que ss não é alterado dentro do loop. Se você sabe que não vai mudar, eu escreveria:
fonte
Arrgh, vai, mesmo em circunstâncias ideais, caramba!
A partir de hoje (janeiro de 2018) e gcc 7.3 e clang 5.0, se você compilar:
Então nós temos:
ss
é um ponteiro constante.ss
é marcado__restrict__
ss
(bem, a menos que viole o__restrict__
).e ainda , ambos os compiladores executam
strlen()
cada iteração desse loop . Surpreendente.Isso também significa que as alusões / ilusões de @Praetorian e @JaredPar não deram certo.
fonte
SIM, em palavras simples. E existe um pequeno não em raras condições em que o compilador deseja, como uma etapa de otimização, se ele descobrir que não há nenhuma alteração feita
ss
. Mas em condições seguras, você deve pensar que sim. Existem algumas situações como emmultithreaded
um programa orientado a eventos, ele pode apresentar erros se você considerar um NÃO. Jogue com segurança, pois isso não vai melhorar muito a complexidade do programa.fonte
Sim.
strlen()
calculado sempre quei
aumenta e não otimiza.O código abaixo mostra por que o compilador não deve otimizar
strlen()
.fonte
strlen
.Podemos testá-lo facilmente:
A condição do loop é avaliada após cada repetição, antes de reiniciar o loop.
Também tome cuidado com o tipo que você usa para lidar com o comprimento das cordas. deve ser o
size_t
que foi definido comounsigned int
em stdio. compará-lo e convertê-lo emint
pode causar alguns problemas graves de vulnerabilidade.fonte
bem, percebi que alguém está dizendo que ele é otimizado por padrão por qualquer compilador moderno "inteligente". A propósito, veja os resultados sem otimização. Eu tentei:
Código C mínimo:
Meu compilador: g ++ (Ubuntu / Linaro 4.6.3-1ubuntu5) 4.6.3
Comando para geração de código de montagem: g ++ -S -masm = intel test.cpp
fonte
restrict
-qualificado. Embora existam alguns casos em que tal otimização seria legítima, o esforço necessário para identificar de forma confiável tais casos na ausência derestrict
, por qualquer medida razoável, quase certamente excederia o benefício. Se o endereço da string tivesse umconst restrict
qualificador, entretanto, isso seria suficiente por si só para justificar a otimização sem ter que olhar para mais nada.Elaborando a resposta de Prætorian, recomendo o seguinte:
auto
porque você não quer se preocupar com o tipo de retorno de strlen. Um compilador C ++ 11 (por exemplogcc -std=c++0x
, não completamente C ++ 11, mas os tipos automáticos funcionam) fará isso por você.i = strlen(s)
porque você deseja comparar0
(veja abaixo)i > 0
porque a comparação com 0 é (ligeiramente) mais rápida que a comparação com qualquer outro número.a desvantagem é que você tem que usar
i-1
para acessar os caracteres da string.fonte