¿Se calculará strlen varias veces si se usa en una condición de bucle?


109

No estoy seguro de si el siguiente código puede causar cálculos redundantes o es específico del compilador.

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

¿ strlen()Se calculará cada vez que iaumente?


14
Supongo que sin una optimización sofisticada que pueda detectar que 'ss' nunca cambia en el ciclo, entonces sí. Es mejor compilar y mirar el ensamblado para ver.
MerickOWA

6
Depende del compilador, del nivel de optimización y de lo que (podría) hacer ssdentro del ciclo.
Hristo Iliev

4
Si el compilador puede probar que ssnunca se modifica, puede sacar el cálculo del ciclo.
Daniel Fischer

10
@Mike: "requiere un análisis en tiempo de compilación de exactamente lo que hace strlen" - strlen es probablemente un intrínseco, en cuyo caso el optimizador sabe lo que hace.
Steve Jessop

3
@MikeSeymour: No hay tal vez, tal vez no. strlen está definido por el estándar del lenguaje C, y su nombre está reservado para el uso definido por el lenguaje, por lo que un programa no es libre de proporcionar una definición diferente. El compilador y el optimizador tienen derecho a asumir que strlen depende únicamente de su entrada y no lo modifica ni a ningún estado global. El desafío de la optimización aquí es determinar que la memoria a la que apunta ss no sea alterada por ningún código dentro del ciclo. Eso es completamente factible con los compiladores actuales, dependiendo del código específico.
Eric Postpischil

Respuestas:


138

Si, strlen() se evaluará en cada iteración. Es posible que, en circunstancias ideales, el optimizador pueda deducir que el valor no cambiará, pero yo personalmente no confiaría en eso.

Haría algo como

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

o posiblemente

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

siempre que la cadena no cambie de longitud durante la iteración. Si es posible, deberá llamar strlen()cada vez o manejarlo con una lógica más complicada.


14
Si sabe que no está manipulando la cuerda, el segundo es mucho más preferible, ya que ese es esencialmente el bucle que realizará de strlentodos modos.
mlibby

26
@alk: si la cadena se puede acortar, ambos son incorrectos.
Mike Seymour

3
@alk: si está cambiando la cadena, un bucle for probablemente no sea la mejor manera de iterar sobre cada carácter. Creo que un ciclo while es más directo y más fácil de administrar el contador de índice.
mlibby

2
Las circunstancias ideales incluyen compilar con GCC en Linux, donde strlense marca como __attribute__((pure))permitir que el compilador elide múltiples llamadas. Atributos de GCC
David Rodríguez - dribeas

6
La segunda versión es la forma ideal y más idiomática. Le permite pasar la cadena solo una vez en lugar de dos, lo que tendrá un rendimiento mucho mejor (especialmente la coherencia de la caché) para cadenas largas.
R .. GitHub DEJA DE AYUDAR A ICE

14

Sí, cada vez que use el bucle. Entonces cada vez calculará la longitud de la cuerda. así que úsalo así:

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

En el código anterior str[i]solo verifica un carácter en particular en la cadena en la ubicacióni cada vez que el bucle comienza un ciclo, por lo que tomará menos memoria y es más eficiente.

Consulte este enlace para obtener más información.

En el siguiente código, cada vez que se ejecute el bucle, strlense contará la longitud de toda la cadena, lo que es menos eficiente, toma más tiempo y requiere más memoria.

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

3
Puedo estar de acuerdo con "[es] más eficiente", pero ¿usa menos memoria? La única diferencia de uso de memoria en la que puedo pensar sería en la pila de llamadas durante la strlenllamada, y si está ejecutando tan apretado, probablemente debería pensar en eludir algunas otras llamadas de función también ...
un CVn

@ MichaelKjörling Bueno, si usa "strlen", entonces en un ciclo tiene que escanear toda la cadena cada vez que se ejecuta el ciclo, mientras que en el código anterior, el "str [ix]", solo escanea un elemento durante cada ciclo del bucle cuya ubicación está representada por "ix". Por lo tanto, se necesita menos memoria que "strlen".
codeDEXTER

1
De hecho, no estoy seguro de que tenga mucho sentido. Una implementación muy ingenua de strlen sería algo así como int strlen(char *s) { int len = 0; while(s[len] != '\0') len++; return len; }que es exactamente lo que está haciendo en el código de su respuesta. No estoy argumentando que iterar sobre la cadena una vez en lugar de dos sea más eficiente en el tiempo , pero no veo que uno u otro use más o menos memoria. ¿O te refieres a la variable utilizada para contener la longitud de la cuerda?
a CVn

@ MichaelKjörling Consulte el código editado anteriormente y el enlace. Y en cuanto a la memoria, cada vez que se ejecuta el bucle, todos y cada uno de los valores que itera se almacenan en la memoria y, en el caso de 'strlen', ya que cuenta toda la cadena una y otra vez, se requiere más memoria para almacenar. y también porque, a diferencia de Java, C ++ no tiene un "recolector de basura". Entonces yo también puedo estar equivocado. consulte el enlace sobre la ausencia del "recolector de basura" en C ++.
codeDEXTER

1
@ aashis2s La falta de un recolector de basura solo juega un papel cuando se crean objetos en el montón. Los objetos de la pila se destruyen tan pronto como el alcance y termina.
Ikke

9

Es posible que un buen compilador no lo calcule siempre, pero no creo que pueda estar seguro de que todos los compiladores lo hagan.

Además de eso, el compilador tiene que saber que eso strlen(ss)no cambia. Esto solo es cierto si ssno se cambia enfor bucle.

Por ejemplo, si usa una función de solo lectura ssen el forbucle pero no declara el ssparámetro -com const, el compilador ni siquiera puede saber que ssno ha cambiado en el bucle y tiene que calcular strlen(ss)en cada iteración.


3
+1: No solo ssno se debe cambiar en el forbucle; no debe ser accesible desde ninguna función llamada en el bucle ni modificada por ella (ya sea porque se pasa como un argumento o porque es una variable global o una variable de ámbito de archivo). La calificación constante también puede ser un factor.
Jonathan Leffler

4
Creo que es muy poco probable que el compilador sepa que 'ss' no cambia. Podría haber punteros perdidos que apuntan a la memoria dentro de 'ss' de la que el compilador no tiene idea de que podrían cambiar 'ss'
MerickOWA

Jonathan tiene razón, una cadena const local podría ser la única forma de que el compilador esté seguro de que no hay forma de que 'ss' cambie.
MerickOWA

2
@MerickOWA: de hecho, esa es una de las cosas para las que restrictestá disponible C99.
Steve Jessop

4
Con respecto a su último para: si llama a una función de solo lectura ssen el bucle for, incluso si se declara su parámetro const char*, el compilador aún necesita recalcular la longitud a menos que (a) sepa que ssapunta a un objeto constante, en lugar de ser un puntero a constante, o (b) puede incluir la función en línea o ver que es de solo lectura. Tomar un const char*parámetro no es una promesa de no modificar los datos apuntados, porque es válido para convertir char*y modificar siempre que el objeto modificado no sea constante y no sea una cadena literal.
Steve Jessop

4

Si sses de tipo const char *y no está descartando el constness dentro del bucle, el compilador solo puede llamar strlenuna vez, si las optimizaciones están activadas. Pero ciertamente no se puede confiar en este comportamiento.

Debe guardar el strlenresultado en una variable y usar esta variable en el ciclo. Si no desea crear una variable adicional, dependiendo de lo que esté haciendo, es posible que se salga con la suya invirtiendo el ciclo para iterar hacia atrás.

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

1
Es un error llamar strlenen absoluto. Simplemente repite hasta que llegues al final.
R .. GitHub DEJA DE AYUDAR A ICE

i > 0? ¿No debería estar i >= 0aquí? Personalmente, también comenzaría strlen(s) - 1si iterar sobre la cadena al revés, entonces la terminación \0no necesita una consideración especial.
a CVn

2
@ MichaelKjörling i >= 0funciona solo si se inicializa strlen(s) - 1, pero luego, si tiene una cadena de longitud cero, el valor inicial se desborda
Praetorian

@ Prætorian, buen punto en la cadena de longitud cero. No consideré ese caso cuando escribí mi comentario. ¿C ++ evalúa la i > 0expresión en la entrada del bucle inicial? Si no es así, entonces tienes razón, el caso de longitud cero definitivamente romperá el ciclo. Si lo hace, "simplemente" obtiene un signo i== -1 <0, por lo que no hay entrada de bucle si el condicional es i >= 0.
a CVn

@ MichaelKjörling Sí, la condición de salida se evalúa antes de ejecutar el bucle por primera vez. strlenEl tipo de retorno no tiene signo, por lo que se (strlen(s)-1) >= 0evalúa como verdadero para cadenas de longitud cero.
Pretoriano

3

Formalmente sí strlen() se espera que se llame para cada iteración.

De todos modos, no quiero negar la posibilidad de que exista alguna optimización inteligente del compilador, que optimizará cualquier llamada sucesiva a strlen () después de la primera.


3

El código de predicado en su totalidad se ejecutará en cada iteración del forciclo. Para memorizar el resultado de la strlen(ss)llamada, el compilador necesitaría saber que al menos

  1. La función strlenestaba libre de efectos secundarios.
  2. La memoria señalada por ssno cambia durante la duración del bucle

El compilador no sabe ninguna de estas cosas y, por lo tanto, no puede memorizar con seguridad el resultado de la primera llamada


Bueno, podría saber esas cosas con el análisis estático, pero creo que su punto es que dicho análisis no está implementado actualmente en ningún compilador de C ++, ¿no?
GManNickG

@GManNickG definitivamente podría resultar el # 1, pero el # 2 es más difícil. Para un solo hilo, sí, definitivamente podría probarlo, pero no para un entorno de subprocesos múltiples.
JaredPar

1
Tal vez estoy siendo terco, pero creo que el número dos también es posible en entornos multiproceso, pero definitivamente no sin un sistema de inferencia tremendamente fuerte. Sin embargo, solo estoy meditando aquí; definitivamente más allá del alcance de cualquier compilador de C ++ actual.
GManNickG

@GManNickG, aunque no creo que sea posible en C / C ++. Podría guardar fácilmente la dirección de ssen a size_to dividirla entre varios bytevalores. Mi hilo tortuoso podría simplemente escribir bytes en esa dirección y el compilador sabría la forma de entender con qué se relaciona ss.
JaredPar

1
@JaredPar: Perdón por continuar, podría afirmar que int a = 0; do_something(); printf("%d",a);no se puede optimizar, sobre la base de que do_something()podría hacer su cosa de int no inicializada, o podría volver a subir por la pila y modificar adeliberadamente. De hecho, gcc 4.5 lo optimiza do_something(); printf("%d",0);con -O3
Steve Jessop

2

si . strlen se calculará cada vez que aumente i.

Si no ha cambiado ss con en el bucle de medios que no afectará a la lógica de lo contrario afectará.

Es más seguro utilizar el siguiente código.

int length = strlen(ss);

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

2

Sí, strlen(ss)calculará la longitud en cada iteración. Si está aumentando el ssde alguna manera y también aumentando el i; habría bucle infinito.


2

Sí, la strlen()función se llama cada vez que se evalúa el bucle.

Si desea mejorar la eficiencia, recuerde siempre guardar todo en variables locales ... Tomará tiempo pero es muy útil ...

Puede usar código como el siguiente:

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

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


2

No es común hoy en día, pero hace 20 años en plataformas de 16 bits, recomendaría esto:

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

Incluso si su compilador no es muy inteligente en la optimización, el código anterior puede resultar en un buen código ensamblador todavía.


1

Si. La prueba no sabe que ss no se cambia dentro del ciclo. Si sabe que no cambiará, escribiría:

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

1

Arrgh, lo hará, incluso en circunstancias ideales, ¡maldición!

A partir de hoy (enero de 2018) y gcc 7.3 y clang 5.0, si compila:

#include <string.h>

void bar(char c);

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

Entonces tenemos:

  • ss es un puntero constante.
  • ss está marcado __restrict__
  • El cuerpo del bucle no puede de ninguna manera tocar la memoria apuntada por ss(bueno, a menos que viole el __restrict__).

y aún así , ambos compiladores ejecutan strlen() cada iteración de ese ciclo . Asombroso.

Esto también significa que las alusiones / ilusiones de @Praetorian y @JaredPar no funcionan.


0

SÍ, en palabras sencillas. Y hay un pequeño no en una condición poco común en la que el compilador desea hacerlo, como paso de optimización si encuentra que no se han realizado cambios en ssabsoluto. Pero en condiciones seguras deberías pensarlo como SÍ. Hay algunas situaciones como en multithreadedun programa impulsado por eventos, puede tener errores si lo considera un NO. Vaya seguro, ya que no mejorará demasiado la complejidad del programa.


0

Si.

strlen()calcula cada vez cuando iaumenta y no optimiza.

El siguiente código muestra por qué el compilador no debería optimizar strlen().

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

Creo que hacer esa modificación en particular nunca altera la longitud de ss, solo su contenido, por lo que (un compilador realmente, realmente inteligente) aún podría optimizar strlen.
Darren Cook

0

Podemos probarlo fácilmente:

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 condición del ciclo se evalúa después de cada repetición, antes de reiniciar el ciclo.

También tenga cuidado con el tipo que usa para manejar la longitud de las cadenas. debe ser lo size_tque se ha definido como unsigned inten stdio. compararlo y convertirlo en intpodría causar algún problema de vulnerabilidad grave.


0

bueno, noté que alguien dice que está optimizado por defecto por cualquier compilador moderno "inteligente". Por cierto, observe los resultados sin optimización. Intenté:
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;
}

Mi compilador: g ++ (Ubuntu / Linaro 4.6.3-1ubuntu5) 4.6.3
Comando para la generación de código ensamblador: 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
     .....

Sería reacio a confiar en cualquier compilador que intentara optimizarlo a menos que la dirección de la cadena estuviera restrictcalificada. Si bien hay algunos casos en los que dicha optimización sería legítima, el esfuerzo requerido para identificar de manera confiable tales casos en ausencia de restrict, con cualquier medida razonable, casi con certeza excedería el beneficio. const restrictSin embargo, si la dirección de la cadena tuviera un calificador, eso sería suficiente en sí mismo para justificar la optimización sin tener que mirar nada más.
supercat

0

Desarrollando la respuesta de Prætorian, recomiendo lo siguiente:

for( auto i = strlen(s)-1; i > 0; --i ) {foo(s[i-1];}
  • autoporque no quiere preocuparse por qué tipo de strlen devuelve. Un compilador de C ++ 11 (p. Ej.gcc -std=c++0x , no completamente C ++ 11 pero los tipos automáticos funcionan) lo hará por usted.
  • i = strlen(s)porque quieres comparar 0(ver más abajo)
  • i > 0 porque la comparación con 0 es (ligeramente) más rápida que la comparación con cualquier otro número.

La desventaja es que tienes que usar i-1para acceder a los caracteres de la cadena.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.