Strlen verrà calcolato più volte se utilizzato in una condizione di loop?


109

Non sono sicuro che il codice seguente possa causare calcoli ridondanti o è specifico del compilatore?

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

Verrà strlen()calcolato ogni volta che iaumenta?


14
Immagino che senza un'ottimizzazione sofisticata in grado di rilevare che "s" non cambia mai nel ciclo, allora sì. È meglio compilare e guardare l'assembly per vedere.
MerickOWA

6
Dipende dal compilatore, dal livello di ottimizzazione e da cosa (potresti) fare ssall'interno del ciclo.
Hristo Iliev

4
Se il compilatore può provare che ssnon è mai stato modificato, può sollevare il calcolo dal ciclo.
Daniel Fischer

10
@ Mike: "richiede un'analisi in fase di compilazione di esattamente ciò che fa strlen" - strlen è probabilmente un elemento intrinseco, nel qual caso l'ottimizzatore sa cosa fa.
Steve Jessop

3
@ MikeSeymour: non c'è forse, forse no. strlen è definito dallo standard del linguaggio C e il suo nome è riservato all'uso definito dal linguaggio, quindi un programma non è libero di fornire una definizione diversa. Il compilatore e l'ottimizzatore hanno il diritto di presumere che strlen dipenda esclusivamente dal suo input e non lo modifichi o da qualsiasi stato globale. La sfida all'ottimizzazione qui sta nel determinare che la memoria puntata da ss non sia alterata da alcun codice all'interno del ciclo. Ciò è del tutto fattibile con gli attuali compilatori, a seconda del codice specifico.
Eric Postpischil

Risposte:


138

Sì, strlen()verrà valutato ad ogni iterazione. È possibile che, in circostanze ideali, l'ottimizzatore sia in grado di dedurre che il valore non cambierà, ma personalmente non ci farei affidamento.

Farei qualcosa di simile

for (int i = 0, n = strlen(ss); i < n; ++i)

o forse

for (int i = 0; ss[i]; ++i)

fintanto che la stringa non cambierà lunghezza durante l'iterazione. Se è possibile, dovrai chiamare strlen()ogni volta o gestirlo attraverso una logica più complicata.


14
Se sai che non stai manipolando la stringa, il secondo è di gran lunga più preferibile poiché è essenzialmente il ciclo che verrà eseguito strlencomunque.
mlibby

26
@alk: se la stringa potrebbe essere accorciata, allora entrambi sono sbagliati.
Mike Seymour

3
@alk: se stai cambiando la stringa, un ciclo for probabilmente non è il modo migliore per iterare su ogni carattere. Penso che un ciclo while sia più diretto e più facile da gestire il contatore dell'indice.
mlibby

2
le circostanze ideali includono la compilazione con GCC sotto linux, dove strlenè contrassegnato per __attribute__((pure))consentire al compilatore di elide più chiamate. Attributi GCC
David Rodríguez - indica il

6
La seconda versione è la forma ideale e più idiomatica. Ti consente di passare la stringa solo una volta anziché due, il che avrà prestazioni molto migliori (in particolare la coerenza della cache) per stringhe lunghe.
R .. GitHub SMETTA DI AIUTARE ICE

14

Sì, ogni volta che usi il loop. Quindi ogni volta calcolerà la lunghezza della stringa. quindi usalo in questo modo:

char str[30];
for ( int i = 0; str[i] != '\0'; i++)
{
//Something;
}

Nel codice precedente str[i]verifica solo un carattere particolare nella stringa nella posizione iogni volta che il ciclo avvia un ciclo, quindi richiederà meno memoria ed è più efficiente.

Vedere questo collegamento per ulteriori informazioni.

Nel codice seguente ogni volta che il ciclo viene eseguito strlenconterà la lunghezza dell'intera stringa che è meno efficiente, richiede più tempo e richiede più memoria.

char str[];
for ( int i = 0; i < strlen(str); i++)
{
//Something;
}

3
Posso essere d'accordo con "[è] più efficiente", ma usi meno memoria? L'unica differenza di utilizzo della memoria a cui posso pensare sarebbe nello stack di chiamate durante la strlenchiamata, e se stai correndo così stretto, probabilmente dovresti pensare di eliminare anche alcune altre chiamate di funzione ...
un CVn

@ MichaelKjörling Bene, se usi "strlen", allora in un ciclo deve scansionare l'intera stringa ogni volta che il ciclo viene eseguito, mentre nel codice sopra "str [ix]", scansiona solo un elemento durante ogni ciclo del loop la cui posizione è rappresentata da "ix". Quindi richiede meno memoria di "strlen".
codeDEXTER

1
Non sono sicuro che abbia molto senso, in realtà. Un'implementazione molto ingenua di strlen sarebbe qualcosa del genere int strlen(char *s) { int len = 0; while(s[len] != '\0') len++; return len; }che è più o meno esattamente ciò che stai facendo nel codice nella tua risposta. Non sto sostenendo che l'iterazione sulla stringa una volta piuttosto che due volte sia più efficiente in termini di tempo , ma non vedo l'una o l'altra che usa più o meno memoria. O ti riferisci alla variabile utilizzata per contenere la lunghezza della stringa?
un CVn

@ MichaelKjörling Si prega di vedere il codice sopra modificato e il collegamento. E per quanto riguarda la memoria, ogni volta che il ciclo viene eseguito ogni valore che itera viene archiviato in memoria e in caso di "strlen" poiché conta l'intera stringa ancora e ancora, richiede più memoria da memorizzare. e anche perché a differenza di Java, il C ++ non ha "Garbage Collector". Allora posso anche sbagliarmi. vedere il collegamento relativo all'assenza di "Garbage Collector" in C ++.
codeDEXTER

1
@ aashis2s La mancanza di un garbage collector gioca un ruolo solo durante la creazione di oggetti sull'heap. Gli oggetti in pila vengono distrutti non appena l'ambito e finisce.
Ikke

9

Un buon compilatore potrebbe non calcolarlo ogni volta, ma non credo che tu possa essere sicuro che lo faccia ogni compilatore.

Inoltre, il compilatore deve sapere che strlen(ss)non cambia. Questo è vero solo se ssnon viene modificato in forloop.

Ad esempio, se si utilizza una funzione di sola lettura ssin un forciclo ma non si dichiara il ssparametro -parametro come const, il compilatore non può nemmeno sapere che ssnon viene modificato nel ciclo e deve calcolare strlen(ss)in ogni iterazione.


3
+1: Non solo ssnon deve essere modificato nel forciclo; non deve essere accessibile e modificato da alcuna funzione chiamata nel ciclo (o perché è passato come argomento, o perché è una variabile globale o una variabile di ambito di file). Anche la qualificazione delle costanti può essere un fattore.
Jonathan Leffler

4
Penso che sia altamente improbabile che il compilatore sappia che "s" non cambia. Potrebbero esserci puntatori vaganti che puntano alla memoria all'interno di "s" di cui il compilatore non ha idea che potrebbero cambiare "
s

Jonathan ha ragione, una stringa const locale potrebbe essere l'unico modo per assicurare al compilatore che non c'è modo di cambiare "s".
MerickOWA

2
@ MerickOWA: in effetti, questa è una delle cose restrictper C99.
Steve Jessop

4
Per quanto riguarda il tuo ultimo paragrafo: se chiami una funzione di sola lettura ssnel ciclo for, anche se il suo parametro viene dichiarato const char*, il compilatore deve comunque ricalcolare la lunghezza a meno che (a) non sappia che sspunta a un oggetto const, invece di essere solo un puntatore a const, o (b) può inline la funzione o altrimenti vedere che è di sola lettura. Prendere un const char*parametro non è una promessa di non modificare i dati puntati, perché è valido eseguire il cast char*e modificare a condizione che l'oggetto modificato non sia const e non sia una stringa letterale.
Steve Jessop

4

Se ssè di tipo const char *e non stai eliminando la constness all'interno del ciclo, il compilatore potrebbe chiamare solo strlenuna volta, se le ottimizzazioni sono attivate. Ma questo non è certamente un comportamento su cui si può contare.

È necessario salvare il strlenrisultato in una variabile e utilizzare questa variabile nel ciclo. Se non vuoi creare una variabile aggiuntiva, a seconda di quello che stai facendo, potresti farla franca invertendo il ciclo per iterare all'indietro.

for( auto i = strlen(s); i > 0; --i ) {
  // do whatever
  // remember value of s[strlen(s)] is the terminating NULL character
}

1
È un errore chiamare strlenaffatto. Ripeti il ​​ciclo finché non raggiungi la fine.
R .. GitHub SMETTA DI AIUTARE ICE

i > 0? Non dovrebbe essere i >= 0qui? Personalmente, inizierei anche da strlen(s) - 1se iterando sulla stringa all'indietro, quindi la terminazione \0non ha bisogno di considerazioni speciali.
un CVn

2
@ MichaelKjörling i >= 0funziona solo se si inizializza su strlen(s) - 1, ma se si dispone di una stringa di lunghezza zero il valore iniziale va in underflow
Praetorian

@ Prætorian, buon punto sulla stringa di lunghezza zero. Non ho considerato quel caso quando ho scritto il mio commento. C ++ valuta l' i > 0espressione all'ingresso del ciclo iniziale? In caso contrario, hai ragione, il caso di lunghezza zero interromperà sicuramente il ciclo. Se lo fa, "semplicemente" ottieni un segno i== -1 <0 quindi nessuna voce di loop se il condizionale è i >= 0.
un CVn

@ MichaelKjörling Sì, la condizione di uscita viene valutata prima di eseguire il ciclo per la prima volta. strlenIl tipo restituito di non è firmato, quindi restituisce (strlen(s)-1) >= 0true per stringhe di lunghezza zero.
Praetorian

3

Formalmente sì, strlen()dovrebbe essere chiamato per ogni iterazione.

Comunque non voglio negare la possibilità che esista qualche intelligente ottimizzazione del compilatore, che ottimizzerà ogni successiva chiamata a strlen () dopo la prima.


3

Il codice del predicato nella sua interezza verrà eseguito a ogni iterazione del forciclo. Per memorizzare il risultato della strlen(ss)chiamata, il compilatore dovrebbe almeno saperlo

  1. La funzione strlenera priva di effetti collaterali
  2. La memoria puntata da ssnon cambia per la durata del ciclo

Il compilatore non conosce nessuna di queste cose e quindi non può memorizzare in modo sicuro il risultato della prima chiamata


Beh, potrebbe sapere queste cose con l'analisi statica, ma penso che il tuo punto sia che tale analisi non è attualmente implementata in nessun compilatore C ++, sì?
GManNickG

@GManNickG potrebbe sicuramente dimostrare # 1 ma # 2 è più difficile. Per un singolo thread sì, potrebbe sicuramente dimostrarlo, ma non per un ambiente multi-thread.
JaredPar

1
Forse sono testardo, ma penso che il numero due sia possibile anche in ambienti multithread, ma sicuramente non senza un sistema di inferenza estremamente forte. Sto solo meditando qui; decisamente oltre lo scopo di qualsiasi compilatore C ++ corrente.
GManNickG

@GManNickG non penso sia possibile anche se in C / C ++. Potrei facilmente nascondere l'indirizzo di ssin a size_to dividerlo tra diversi bytevalori. Il mio subdolo thread potrebbe quindi scrivere semplicemente byte in quell'indirizzo e il compilatore avrebbe saputo come capire che si riferiva a ss.
JaredPar

1
@ JaredPar: Mi dispiace continuare, potresti affermare che int a = 0; do_something(); printf("%d",a);non può essere ottimizzato, sulla base del fatto che do_something()potrebbe fare la tua cosa int non inizializzata, o potrebbe eseguire il backup dello stack e modificare adeliberatamente. In effetti, gcc 4.5 lo ottimizza do_something(); printf("%d",0);con -O3
Steve Jessop

2

. strlen verrà calcolato ogni volta che aumenta i.

Se non hai cambiato ss con nel ciclo significa che non influenzerà la logica altrimenti influirà.

È più sicuro utilizzare il codice seguente.

int length = strlen(ss);

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

2

Sì, strlen(ss)calcolerà la lunghezza ad ogni iterazione. Se stai aumentando il ssin qualche modo e anche aumentando il i; ci sarebbe un ciclo infinito.


2

Sì, la strlen()funzione viene chiamata ogni volta che viene valutato il ciclo.

Se vuoi migliorare l'efficienza ricordati sempre di salvare tutto nelle variabili locali ... Ci vorrà tempo ma è molto utile ..

Puoi usare il codice come di seguito:

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

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

2

Sì, strlen(ss)verrà calcolato ogni volta che viene eseguito il codice.


2

Non comune al giorno d'oggi ma 20 anni fa su piattaforme a 16 bit, consiglierei questo:

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

Anche se il tuo compilatore non è molto intelligente nell'ottimizzazione, il codice sopra può risultare ancora in un buon codice assembly.


1

Sì. Il test non sa che ss non viene modificato all'interno del ciclo. Se sai che non cambierà, scriverei:

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

1

Arrgh, lo farà, anche in circostanze ideali, dannazione!

Ad oggi (gennaio 2018), e gcc 7.3 e clang 5.0, se compili:

#include <string.h>

void bar(char c);

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

Quindi, abbiamo:

  • ss è un puntatore costante.
  • ss è contrassegnato __restrict__
  • Il corpo del loop non può in alcun modo toccare la memoria puntata da ss(beh, a meno che non violi il __restrict__).

e ancora , entrambi i compilatori eseguono strlen() ogni singola iterazione di quel ciclo . Sorprendente.

Ciò significa anche che le allusioni / i pio desiderio di @Praetorian e @JaredPar non hanno effetto.


0

SÌ, in parole semplici. E c'è un piccolo no in rare condizioni in cui il compilatore desidera, come passaggio di ottimizzazione se rileva che non sono state apportate modifiche ss. Ma in condizioni di sicurezza dovresti pensarlo come SÌ. Ci sono alcune situazioni come il programma in multithreadede event driven, potrebbe diventare difettoso se lo consideri un NO. Gioca sul sicuro perché non migliorerà troppo la complessità del programma.


0

Sì.

strlen()calcolato ogni volta che iaumenta e non si ottimizza.

Il codice seguente mostra perché il compilatore non dovrebbe ottimizzare strlen().

for ( int i = 0; i < strlen(ss); ++i )
{
   // Change ss string.
   ss[i] = 'a'; // Compiler should not optimize strlen().
}

Penso che fare quella particolare modifica non altera mai la lunghezza di ss, solo il suo contenuto, quindi (un compilatore davvero, davvero intelligente) potrebbe ancora ottimizzare strlen.
Darren Cook

0

Possiamo facilmente testarlo:

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

La condizione del loop viene valutata dopo ogni ripetizione, prima di riavviare il loop.

Fai anche attenzione al tipo che usi per gestire la lunghezza delle stringhe. dovrebbe essere size_tche è stato definito come unsigned intin stdio. confrontarlo e trasmetterlo a intpotrebbe causare seri problemi di vulnerabilità.


0

beh, ho notato che qualcuno sta dicendo che è ottimizzato di default da qualsiasi compilatore moderno "intelligente". A proposito, guarda i risultati senza ottimizzazione. Ho provato:
codice C minimo:

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

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

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

Il mio compilatore: g ++ (Ubuntu / Linaro 4.6.3-1ubuntu5) 4.6.3
Comando per la generazione del codice assembly: 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
     .....

Sarei restio a fidarmi di qualsiasi compilatore che cercasse di ottimizzarlo a meno che l'indirizzo della stringa non fosse restrict-qualificato. Sebbene vi siano alcuni casi in cui tale ottimizzazione sarebbe legittima, lo sforzo richiesto per identificare in modo affidabile tali casi in assenza di tali casi restrictsupererebbe, con qualsiasi misura ragionevole, quasi certamente il vantaggio. Se l'indirizzo della stringa avesse un const restrictqualificatore, tuttavia, questo sarebbe sufficiente di per sé per giustificare l'ottimizzazione senza dover guardare ad altro.
supercat

0

Elaborando la risposta di Prætorian, consiglio quanto segue:

for( auto i = strlen(s)-1; i > 0; --i ) {foo(s[i-1];}
  • autoperché non vuoi preoccuparti del tipo restituito da strlen. Un compilatore C ++ 11 (ad esempio gcc -std=c++0x, non completamente C ++ 11 ma i tipi auto funzionano) lo farà per te.
  • i = strlen(s)perché vuoi confrontare con 0(vedi sotto)
  • i > 0 perché il confronto con 0 è (leggermente) più veloce del confronto con qualsiasi altro numero.

lo svantaggio è che devi usare i-1per accedere ai caratteri della stringa.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.