Bir döngü koşulunda kullanılırsa strlen birden çok kez hesaplanacak mı?


109

Aşağıdaki kodun gereksiz hesaplamalara neden olup olamayacağından emin değilim, yoksa derleyiciye özel mi?

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

Will strlen()zaman her zaman hesaplanabilir iartar?


14
Tahmin edeceğim, karmaşık bir optimizasyon olmadan bunların döngüde asla değişmediğini tespit edebilir, o zaman evet. Derlemek için en iyisi ve görmek için montaja bakın.
MerickOWA

6
Derleyiciye, optimizasyon düzeyine ve ssdöngü içinde ne yapabileceğinize (yapabileceğiniz) bağlıdır .
Hristo Iliev

4
Derleyici ssbunun asla değiştirilmediğini kanıtlayabilirse , hesaplamayı döngüden çıkarabilir.
Daniel Fischer

10
@Mike: "strlen'in tam olarak ne yaptığının derleme zamanı analizine ihtiyacı var" - strlen muhtemelen bir içseldir, bu durumda optimizer ne yaptığını bilir.
Steve Jessop

3
@MikeSeymour: Belki yoktur, belki yoktur. strlen, C dil standardı tarafından tanımlanır ve adı, dil tarafından tanımlanan kullanım için ayrılmıştır, bu nedenle bir program farklı bir tanım sağlamakta özgür değildir. Derleyici ve iyileştirici, strlen'in yalnızca girdisine bağlı olduğunu ve onu veya herhangi bir genel durumu değiştirmediğini varsayma hakkına sahiptir. Buradaki optimizasyonun zorluğu, ss tarafından gösterilen belleğin döngü içindeki herhangi bir kod tarafından değiştirilmediğini belirlemektir. Bu, belirli koda bağlı olarak mevcut derleyiciler için tamamen uygulanabilir.
Eric Postpischil

Yanıtlar:


138

Evet, strlen()her yinelemede değerlendirilecektir. İdeal koşullar altında, optimizasyon uzmanının değerin değişmeyeceği sonucuna varması mümkündür, ancak ben şahsen buna güvenmem.

Gibi bir şey yaparım

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

veya muhtemelen

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

iterasyon sırasında dizenin uzunluğu değişmediği sürece. Mümkünse, strlen()her seferinde aramanız veya daha karmaşık mantıkla halledmeniz gerekir.


14
Dizeyi değiştirmediğinizi biliyorsanız, ikincisi çok daha fazla tercih edilir, çünkü bu aslında strlenyine de gerçekleştirilecek döngüdür .
mlibby

26
@alk: Dize kısaltılabilirse, ikisi de yanlıştır.
Mike Seymour

3
@alk: dizeyi değiştiriyorsanız, for döngüsü muhtemelen her karakter üzerinde yinelemenin en iyi yolu değildir. Bir while döngüsünün daha doğrudan ve indeks sayacını yönetmenin daha kolay olduğunu düşünüyorum.
mlibby

2
ideal koşullar , derleyicinin birden çok çağrıyı atlamasına izin verdiği strlenişaretlenen linux altında GCC ile derlemeyi içerir __attribute__((pure)). GCC Nitelikleri
David Rodríguez - dribeas

6
İkinci versiyon, ideal ve en deyimsel formdur. Uzun dizeler için çok daha iyi performansa (özellikle önbellek tutarlılığına) sahip olacak şekilde dizeyi iki yerine yalnızca bir kez geçirmenize izin verir.
R .. GitHub BUZA YARDIM ETMEYİ DURDUR

14

Evet, döngüyü her kullandığınızda. Daha sonra her seferinde dizginin uzunluğunu hesaplayacaktır. öyleyse şu şekilde kullanın:

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

Yukarıdaki kodda , döngü bir döngüyü her str[i]başlattığında konumdaki dizedeki yalnızca belirli bir karakteri doğrular i, böylece daha az bellek alır ve daha verimlidir.

Daha fazla bilgi için bu Bağlantıya bakın .

Aşağıdaki kodda, döngü her çalıştırıldığında strlen, daha az verimli olan, daha fazla zaman alan ve daha fazla bellek alan tüm dizenin uzunluğunu sayacaktır.

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

3
"[İt] daha verimli" fikrine katılıyorum, ancak daha az bellek kullanıyor musunuz? Düşünebildiğim tek hafıza kullanım farkı, çağrı sırasında çağrı yığınında olacaktır strlenve bu kadar sıkı koşuyorsanız, muhtemelen birkaç başka işlev çağrısını da atlamayı düşünmelisiniz ...
bir CVn

@ MichaelKjörling Eğer "strlen" kullanırsanız, o zaman bir döngüde, döngü her çalıştığında tüm dizeyi taramak zorundadır, oysa yukarıdaki kodda "str [ix]", her döngüde yalnızca bir öğeyi tarar konumu "ix" ile gösterilen döngü. Böylece "strlen" den daha az bellek alır.
codeDEXTER

1
Aslında bunun çok mantıklı olduğundan emin değilim. Çok naif bir strlen uygulaması, int strlen(char *s) { int len = 0; while(s[len] != '\0') len++; return len; }cevabınızdaki kodda yaptığınız şeyin hemen hemen aynısı gibi bir şey olacaktır . Dizeyi iki kez yerine bir kez yinelemenin zaman açısından daha verimli olduğunu tartışmıyorum, ancak birinin veya diğerinin daha fazla veya daha az bellek kullandığını görmüyorum. Yoksa dize uzunluğunu tutmak için kullanılan değişkenden mi bahsediyorsunuz?
49'da bir CVn

@ MichaelKjörling Lütfen yukarıda düzenlenen koda ve bağlantıya bakın. Ve belleğe gelince - döngü her çalıştığında ve yinelenen her değer bellekte depolanır ve 'strlen' durumunda tüm dizeyi tekrar sayarken ve tekrar saklamak için daha fazla bellek gerektirir. ve ayrıca Java'dan farklı olarak C ++ 'da "Çöp Toplayıcı" bulunmadığından. O zaman ben de yanılıyor olabilirim. C ++ 'da "Çöp Toplayıcı" yokluğuyla ilgili bağlantıya bakın .
codeDEXTER

1
@ aashis2s Çöp toplayıcının olmaması, yalnızca yığın üzerinde nesne oluştururken bir rol oynar. Yığın üzerindeki nesneler, kapsam ve biter bitmez yok edilir.
Ikke

9

İyi bir derleyici bunu her seferinde hesaplamayabilir, ancak her derleyicinin bunu yaptığından emin olabileceğinizi sanmıyorum.

Buna ek olarak, derleyicinin bilmesi gerekir, bu strlen(ss)değişmez. Bu yalnızca döngüde ssdeğiştirilmezse geçerlidir for.

Üzerinde salt okunur işlevini kullanın Örneğin, ssiçinde fordöngü ama beyan yok ss-parametre olarak constbile bilemez derleyici, ssdöngü içinde değişti ve hesaplamak zorundadır değildir strlen(ss)her tekrarında.


3
+1: Yalnızca döngüde ssdeğiştirilmemelidir for; döngüde çağrılan herhangi bir işlevden erişilebilmemeli ve değiştirilmemelidir (ya bir argüman olarak iletildiği için ya da global bir değişken ya da bir dosya kapsamı değişkeni olduğu için). Sabit yeterlilik de bir faktör olabilir.
Jonathan Leffler

4
Derleyicinin "ss" nin değişmeyeceğini bilmesinin pek olası olmadığını düşünüyorum. Derleyicinin 'ss'yi değiştirebileceğine dair hiçbir fikri olmayan' ss 'içindeki belleğe işaret eden başıboş işaretçiler olabilir
MerickOWA

Jonathan haklı, derleyicinin 'ss'nin değişmesi için hiçbir yol olmadığından emin olmasının tek yolu yerel bir const dizesi olabilir.
MerickOWA

2
@MerickOWA: gerçekten, bu restrictC99'da olan şeylerden biri .
Steve Jessop

4
Son paragraf ilgili olarak: Üzerinde salt okunur işlevini çağırırsanız ssiçin-döngü içinde, daha sonra parametre olarak ilan olsa bile const char*, derleyici hala (a) bilir ya sürece uzunluğunu hesaplamak zorundadır ssconst nesnesine puan sadece sabit bir işaretçi olmanın tersine veya (b) işlevi satır içi yapabilir veya başka şekilde salt okunur olduğunu görebilir. Bir çıkarak const char*parametre olduğunu değil o kadar döküm için geçerli olduğundan, veri işaret değiştirmek için değil bir söz char*ve değiştirilmiş nesne const değildir ve bir dize olmamak kaydıyla, değiştirmek.
Steve Jessop

4

Eğer sstiptedir const char *ve uzakta yayın yapmıyor constderleyici yalnızca diyebilirsiniz döngü içinde lık strlenoptimizasyonlar açıksa, bir kere. Ancak bu kesinlikle güvenilebilecek bir davranış değildir.

Sonucu strlenbir değişkene kaydetmeli ve bu değişkeni döngüde kullanmalısınız. Ek bir değişken oluşturmak istemiyorsanız, ne yaptığınıza bağlı olarak, geriye doğru yinelemek için döngüyü tersine çevirmekten kurtulabilirsiniz.

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

1
Aramak bir hata strlen. Sadece sonuna gelene kadar döngü yapın.
R .. GitHub BUZA YARDIM ETMEYİ DURDUR

i > 0? i >= 0Burada olmamalı mı ? Kişisel olarak, strlen(s) - 1dizgiyi geriye doğru yinelemeye de başlayabilirim , o zaman sonlandırmanın \0özel bir değerlendirmeye ihtiyacı yoktur.
19'da bir CVn

2
@ MichaelKjörling i >= 0yalnızca ile başlatırsanız çalışır strlen(s) - 1, ancak sıfır uzunlukta bir dizeniz varsa başlangıç ​​değeri yetersiz kalır
Praetorian

@ Prætorian, sıfır uzunluklu ipte iyi bir nokta. Yorumumu yazarken bu durumu dikkate almadım. C ++ i > 0, ilk döngü girişindeki ifadeyi değerlendirir mi? Olmazsa, o zaman haklısınız, sıfır uzunluk durumu kesinlikle döngüyü kıracaktır. Eğer öyleyse, "basitçe" bir işaretli i== -1 <0 alırsınız, böylece koşullu ise döngü girişi olmaz i >= 0.
bir CVn

@ MichaelKjörling Evet, çıkış koşulu döngü ilk kez çalıştırılmadan önce değerlendirilir. strlendönüş türü işaretsiz olduğundan, (strlen(s)-1) >= 0sıfır uzunluklu dizeler için doğru olarak değerlendirilir.
Praetorian

3

Resmen evet, strlen()her yinelemede çağrılması bekleniyor.

Her neyse, ilkinden sonraki strlen () 'e yapılan ardışık çağrıları ortadan kaldıracak bazı akıllı derleyici optimizasyonlarının var olma olasılığını reddetmek istemiyorum.


3

Yüklem kodu bütünüyle fordöngünün her yinelemesinde çalıştırılacaktır . Çağrının sonucunu ezberlemek için strlen(ss)derleyicinin en azından şunu bilmesi gerekir:

  1. Fonksiyon strlenyan etkisizdi
  2. İle gösterilen hafıza ssdöngü süresince değişmez

Derleyici bu iki şeyi de bilmiyor ve bu nedenle ilk çağrının sonucunu güvenli bir şekilde hatırlayamıyor.


Eh o olabilir statik analizi ile bunları biliyorum, ama senin nokta böyle analiz şu anda evet, derleyici ++ herhangi C uygulanmadı olduğunu düşünüyorum?
GManNickG

@GManNickG kesinlikle # 1'i kanıtlayabilirdi ama # 2 daha zordur. Tek bir iş parçacığı için evet, bunu kesinlikle kanıtlayabilir, ancak çok iş parçacıklı bir ortam için değil.
JaredPar

1
Belki inatçıyım ama sanırım iki numara çok iş parçacıklı ortamlarda da mümkün, ama kesinlikle çok güçlü bir çıkarım sistemi olmadan değil. Sadece burada derin düşünmek; kesinlikle mevcut herhangi bir C ++ derleyicisinin kapsamının ötesinde.
GManNickG

@GManNickG C / C ++ ile mümkün olduğunu sanmıyorum. Çok kolay adresini saklamak olabilir ssbir içine size_tveya birkaç aralarında o kadar bölmek bytedeğerler. Benim aldatıcı iş parçacığım o zaman bu adrese baytlar yazabilir ve derleyici bununla ilgili olduğunu anlama yolunu bilirdi ss.
JaredPar

1
@JaredPar: Kusura bakmayın, int a = 0; do_something(); printf("%d",a);bunun optimize do_something()edilemeyeceğini, başlatılmamış int işinizi yapabileceğini veya yığını yedekleyip akasıtlı olarak değiştirebileceğini iddia edebilirsiniz . Aslında gcc 4.5, do_something(); printf("%d",0);-O3 ile optimize ediyor
Steve Jessop

2

Evet . strlen her zaman arttığımda hesaplanacak.

Eğer varsa ss değişmedi ile döngü içinde bu yollarla mantığını etkilemez aksi takdirde etkileyecektir.

Aşağıdaki kodu kullanmak daha güvenlidir.

int length = strlen(ss);

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

2

Evet, strlen(ss)her yinelemede uzunluğu hesaplayacaktır. Bir şekilde artırıyor ssve aynı zamanda artırıyorsanız i; sonsuz döngü olacaktır.


2

Evet, strlen()işlev denir her zaman döngü değerlendirilir.

Verimliliği artırmak istiyorsanız, her şeyi yerel değişkenlere kaydetmeyi her zaman unutmayın ... Zaman alacaktır ama çok yararlıdır ..

Kodu aşağıdaki gibi kullanabilirsiniz:

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

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


2

Bugünlerde yaygın değil ama 20 yıl önce 16 bit platformlarda, şunu tavsiye ederim:

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

Derleyiciniz optimizasyon konusunda çok akıllı olmasa bile, yukarıdaki kod henüz iyi bir montaj koduyla sonuçlanabilir.


1

Evet. Test, ss'nin döngü içinde değişmediğini bilmiyor. Eğer değişmeyeceğini biliyorsanız, o zaman yazacağım:

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

1

Arrgh, ideal koşullarda bile kahretsin!

Bugün (Ocak 2018) ve gcc 7.3 ve clang 5.0 itibariyle, derlerseniz:

#include <string.h>

void bar(char c);

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

Böylece sahibiz:

  • ss sabit bir göstericidir.
  • ss işaretlendi __restrict__
  • Döngü gövdesi hiçbir şekilde işaret edilen belleğe dokunamaz ss(iyi, ihlal etmediği sürece __restrict__).

ve yine de , her iki derleyici strlen() bu döngünün her bir yinelemesini yürütür . İnanılmaz.

Bu aynı zamanda @Praetorian ve @JaredPar'ın imaları / temenni düşüncelerinin işe yaramayacağı anlamına gelir.


0

EVET, basit bir deyişle. Ve derleyicinin içinde hiç değişiklik olmadığını fark ederse bir optimizasyon adımı olarak, nadiren de olsa küçük bir hayır yoktur ss. Ancak güvenli durumda EVET olarak düşünmelisiniz. multithreadedOlay güdümlü program gibi bazı durumlar vardır, bunu bir HAYIR olarak değerlendirirseniz hatalı olabilir. Program karmaşıklığını çok fazla geliştirmeyeceği için güvenli oynayın.


0

Evet.

strlen()iarttığında ve optimize edilmediğinde her seferinde hesaplanır .

Aşağıdaki kod, derleyicinin neden optimize etmemesi gerektiğini gösterir strlen().

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

Bence bu özel değişikliği yapmak ss uzunluğunu asla değiştirmez, sadece içeriğini değiştirmez, bu yüzden (gerçekten, gerçekten zeki) bir derleyici yine de optimize edebilir strlen.
Darren Cook

0

Kolayca test edebiliriz:

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

Döngü koşulu, döngüyü yeniden başlatmadan önce her tekrardan sonra değerlendirilir.

Ayrıca, dizelerin uzunluğunu işlemek için kullandığınız tür konusunda dikkatli olun. standart size_tolarak tanımlanmış olması gerekir unsigned int. karşılaştırmak ve dönüştürmek intbazı ciddi güvenlik açığı sorunlarına neden olabilir.


0

Pekala, birinin varsayılan olarak herhangi bir "akıllı" modern derleyici tarafından optimize edildiğini söylediğini fark ettim. Bu arada, sonuçlara optimizasyon yapmadan bakın. Denedim:
Minimum C kodu:

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

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

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

Derleyicim: g ++ (Ubuntu / Linaro 4.6.3-1ubuntu5) 4.6.3
Derleme kodu oluşturma komutu: 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
     .....

Dizenin adresi restrict-kalifiye olmadıkça onu optimize etmeye çalışan herhangi bir derleyiciye güvenmek istemem. Bu tür bir optimizasyonun meşru olacağı bazı durumlar olsa da, bu tür vakaların yokluğunda güvenilir bir şekilde tespit edilmesi için gereken çaba restrict, herhangi bir makul önlemle, neredeyse kesinlikle faydayı aşacaktır. Ancak dizenin adresinin bir const restrictniteleyicisi varsa, bu kendi başına optimizasyonu başka bir şeye bakmak zorunda kalmadan doğrulamak için yeterli olacaktır.
supercat

0

Prætorian'ın cevabını detaylandırırken şunları tavsiye ederim:

for( auto i = strlen(s)-1; i > 0; --i ) {foo(s[i-1];}
  • autoçünkü strlen'in hangi türden döndüğünü umursamak istemezsiniz. Bir C ++ 11 derleyicisi (örneğin gcc -std=c++0x, tamamen C ++ 11 değil, otomatik türler çalışır) sizin için bunu yapacaktır.
  • i = strlen(s)karşılaştırmak istediğiniz için 0(aşağıya bakın)
  • i > 0 çünkü 0 ile karşılaştırma, diğer herhangi bir sayıya kıyasla (biraz) daha hızlıdır.

dezavantajı, i-1dize karakterlerine erişmek için kullanmanız gerekmesidir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.