O strlen será calculado várias vezes se usado em uma condição de loop?

109

Não tenho certeza se o código a seguir pode causar cálculos redundantes ou é específico do compilador?

for (int i = 0; i < strlen(ss); ++i)
{
    // blabla
}

Será strlen()calculado sempre que iaumenta?

margarida
fonte
14
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.

Mike Seymour
fonte
14
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.

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.

char str[];
for ( int i = 0; i < strlen(str); i++)
{
//Something;
}
codeDEXTER
fonte
3
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.

Misch
fonte
3
+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
}
Pretoriano
fonte
1
É 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.

alk
fonte
3

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

  1. A função não strlentinha efeitos colaterais
  2. 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

JaredPar
fonte
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
}
Kalai Selvan Ravi
fonte
2

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.

Kumar Shorav
fonte
2

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:

String str="ss";
int l = strlen(str);

for ( int i = 0; i < l ; i++ )
{
    // blablabla
}
Rajan
fonte
2

Sim, strlen(ss)será calculado sempre que o código for executado.

Hisham Muneer
fonte
2

Não é comum hoje em dia, mas há 20 anos em plataformas de 16 bits, eu recomendo isso:

for ( char* p = str; *p; p++ ) { /* ... */ }

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.

Luciano
fonte
1

Sim. O teste não sabe que ss não é alterado dentro do loop. Se você sabe que não vai mudar, eu escreveria:

int stringLength = strlen (ss); 
for ( int i = 0; i < stringLength; ++ i ) 
{
  // blabla 
} 
DanS
fonte
1

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:

#include <string.h>

void bar(char c);

void foo(const char* __restrict__ ss) 
{
    for (int i = 0; i < strlen(ss); ++i) 
    {
        bar(*ss);
    }
}    

Então nós temos:

  • ss é um ponteiro constante.
  • ss é marcado __restrict__
  • O corpo do loop não pode de forma alguma tocar a memória apontada por 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.

einpoklum
fonte
0

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.

Pervez Alam
fonte
0

Sim.

strlen() calculado sempre que i aumenta e não otimiza.

O código abaixo mostra por que o compilador não deve otimizar strlen().

for ( int i = 0; i < strlen(ss); ++i )
{
   // Change ss string.
   ss[i] = 'a'; // Compiler should not optimize strlen().
}
Amir Saniyan
fonte
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.
Darren Cook
0

Podemos testá-lo facilmente:

char nums[] = "0123456789";
size_t end;
int i;
for( i=0, end=strlen(nums); i<strlen(nums); i++ ) {
    putchar( nums[i] );
    num[--end] = 0;
}

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.

Rsh
fonte
0

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:

#include <stdio.h>
#include <string.h>

int main()
{
 char *s="aaaa";

 for (int i=0; i<strlen(s);i++)
  printf ("a");
 return 0;
}

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
     .....
Tebe
fonte
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.

Steffen
fonte