Akankah strlen dihitung beberapa kali jika digunakan dalam kondisi loop?


109

Saya tidak yakin apakah kode berikut dapat menyebabkan kalkulasi yang berlebihan, atau apakah itu khusus kompiler?

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

Akan strlen()dihitung setiap kali ikenaikan?


14
Saya akan menebak bahwa tanpa pengoptimalan canggih yang dapat mendeteksi itu tidak akan pernah berubah dalam perulangan, maka ya. Terbaik untuk dikompilasi, dan lihat perakitan untuk melihat.
MerickOWA

6
Itu tergantung pada kompiler, pada tingkat optimasi dan pada apa yang Anda (mungkin) lakukan ssdi dalam loop.
Hristo Iliev

4
Jika kompilator dapat membuktikan bahwa ssia tidak pernah dimodifikasi, ia dapat mengeluarkan komputasi dari loop.
Daniel Fischer

10
@ Mike: "memerlukan analisis waktu kompilasi dari apa yang sebenarnya dilakukan strlen" - strlen mungkin adalah intrinsik, dalam hal ini pengoptimal tahu apa yang dilakukannya.
Steve Jessop

3
@MikeSeymour: Tidak mungkin, mungkin juga tidak. strlen didefinisikan oleh standar bahasa C, dan namanya dicadangkan untuk penggunaan yang ditentukan oleh bahasa tersebut, sehingga program tidak bebas untuk menyediakan definisi yang berbeda. Kompilator dan pengoptimal berhak menganggap strlen hanya bergantung pada inputnya dan tidak mengubahnya atau status global apa pun. Tantangan untuk pengoptimalan di sini adalah menentukan bahwa memori yang ditunjukkan oleh ss tidak diubah oleh kode apa pun di dalam loop. Itu sepenuhnya dapat dilakukan dengan kompiler saat ini, bergantung pada kode spesifik.
Eric Postpischil

Jawaban:


138

Ya, strlen()akan dievaluasi pada setiap iterasi. Ada kemungkinan bahwa, dalam keadaan ideal, pengoptimal mungkin dapat menyimpulkan bahwa nilainya tidak akan berubah, tetapi saya pribadi tidak akan bergantung pada itu.

Saya akan melakukan sesuatu seperti

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

atau mungkin

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

selama string tidak akan berubah panjang selama iterasi. Jika mungkin, Anda harus menelepon strlen()setiap kali, atau menanganinya melalui logika yang lebih rumit.


14
Jika Anda tahu Anda tidak memanipulasi string, yang kedua jauh lebih disukai karena pada dasarnya itulah loop yang akan dilakukan strlenolehnya.
mlibby

26
@alk: Jika string mungkin dipersingkat, maka keduanya salah.
Mike Seymour

3
@alk: jika Anda mengubah string, perulangan for mungkin bukan cara terbaik untuk mengulang setiap karakter. Menurut saya, loop sementara lebih langsung dan lebih mudah untuk mengelola penghitung indeks.
mlibby

2
keadaan yang ideal mencakup kompilasi dengan GCC di bawah linux, di mana strlenditandai __attribute__((pure))mengizinkan kompiler untuk memanggil banyak panggilan. Atribut GCC
David Rodríguez - dribeas

6
Versi kedua adalah bentuk yang ideal dan paling idiomatis. Ini memungkinkan Anda untuk melewatkan string hanya sekali daripada dua kali, yang akan memiliki kinerja yang jauh lebih baik (terutama koherensi cache) untuk string yang panjang.
R .. GitHub STOP HELPING ICE

14

Ya, setiap kali Anda menggunakan loop. Maka itu akan setiap kali menghitung panjang string. jadi gunakan seperti ini:

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

Pada kode di atas str[i]hanya memverifikasi satu karakter tertentu dalam string di lokasi isetiap kali loop memulai siklus, sehingga akan memakan lebih sedikit memori dan lebih efisien.

Lihat Tautan ini untuk informasi lebih lanjut.

Pada kode di bawah ini setiap kali loop berjalan strlenakan menghitung panjang keseluruhan string yang kurang efisien, membutuhkan lebih banyak waktu dan membutuhkan lebih banyak memori.

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

3
Saya setuju dengan "[itu] lebih efisien", tetapi menggunakan lebih sedikit memori? Satu-satunya perbedaan penggunaan memori yang dapat saya pikirkan akan berada di tumpukan panggilan selama strlenpanggilan, dan jika Anda menjalankannya sesak itu, Anda mungkin harus berpikir untuk memilih beberapa panggilan fungsi lainnya juga ...
CVn

@ MichaelKjörling Jika Anda menggunakan "strlen", maka dalam satu loop harus memindai seluruh string setiap kali loop berjalan, sedangkan dalam kode di atas "str [ix]", itu hanya memindai satu elemen selama setiap siklus loop yang lokasinya diwakili oleh "ix". Jadi dibutuhkan lebih sedikit memori daripada "strlen".
kodeDEXTER

1
Saya tidak yakin itu masuk akal, sebenarnya. Implementasi strlen yang sangat naif akan menjadi sesuatu int strlen(char *s) { int len = 0; while(s[len] != '\0') len++; return len; }yang persis seperti yang Anda lakukan dalam kode dalam jawaban Anda. Saya tidak berargumen bahwa mengulang string sekali daripada dua kali lebih efisien waktu , tetapi saya tidak melihat satu atau yang lain menggunakan lebih banyak atau lebih sedikit memori. Atau apakah Anda mengacu pada variabel yang digunakan untuk menahan panjang string?
CVn

@ MichaelKjörling Silakan lihat kode yang diedit di atas dan tautannya. Dan untuk memori- setiap kali loop berjalan, setiap nilai yang diiterasi disimpan dalam memori dan dalam kasus 'strlen' karena menghitung seluruh string berulang kali, diperlukan lebih banyak memori untuk disimpan. dan juga karena tidak seperti Java, C ++ tidak memiliki "Pengumpul Sampah". Maka saya bisa salah juga. lihat tautan mengenai tidak adanya "Pengumpul Sampah" di C ++.
codeDEXTER

1
@ aashis2s Kurangnya pengumpul sampah hanya berperan saat membuat objek di heap. Objek di tumpukan akan dihancurkan segera setelah cakupan dan berakhir.
Ikke

9

Kompiler yang baik mungkin tidak menghitungnya setiap saat, tetapi menurut saya Anda tidak dapat memastikan, bahwa setiap kompiler melakukannya.

Selain itu, kompilator harus tahu, itu strlen(ss)tidak berubah. Ini hanya benar jika sstidak diubah dalam forloop.

Misalnya, jika Anda menggunakan fungsi hanya-baca di ssdalam forperulangan tetapi tidak mendeklarasikan ss-parameter sebagai const, kompilator bahkan tidak dapat mengetahui bahwa sstidak diubah dalam perulangan dan harus menghitung strlen(ss)di setiap iterasi.


3
+1: Tidak hanya sstidak boleh diubah di forloop; itu tidak boleh dapat diakses dari dan diubah oleh fungsi apa pun yang dipanggil dalam loop (baik karena diteruskan sebagai argumen, atau karena variabel global atau variabel cakupan file). Kualifikasi konstitusi juga bisa menjadi faktor.
Jonathan Leffler

4
Saya rasa sangat tidak mungkin kompilator bisa mengetahui bahwa 'ss' tidak berubah. Mungkin ada petunjuk tersesat yang mengarah ke memori di dalam 'ss' yang tidak diketahui oleh kompiler yang dapat mengubah 'ss'
MerickOWA

Jonathan benar, string const lokal mungkin merupakan satu-satunya cara bagi kompilator untuk yakin bahwa tidak ada cara untuk 'ss' berubah.
MerickOWA

2
@MerickOWA: memang, itulah salah satu hal yang restrictada di C99.
Steve Jessop

4
Mengenai para terakhir Anda: jika Anda memanggil fungsi read-only ssdi for-loop, bahkan jika parameternya dideklarasikan const char*, compiler masih perlu menghitung ulang panjangnya kecuali jika (a) ia tahu bahwa ssmenunjuk ke objek const, sebagai lawan hanya menjadi pointer-to-const, atau (b) itu dapat menyebariskan fungsi atau melihat bahwa itu hanya-baca. Mengambil const char*parameter bukanlah janji untuk tidak mengubah data yang ditunjuk, karena valid untuk mentransmisikan char*dan memodifikasi asalkan objek yang dimodifikasi bukan const dan bukan literal string.
Steve Jessop

4

Jika ssadalah tipe const char *dan Anda tidak membuang semua kebutuhan constdalam loop, kompilator mungkin hanya memanggil strlensekali, jika pengoptimalan dihidupkan. Tapi ini jelas bukan perilaku yang bisa diandalkan.

Anda harus menyimpan strlenhasilnya dalam sebuah variabel dan menggunakan variabel ini dalam perulangan. Jika Anda tidak ingin membuat variabel tambahan, tergantung pada apa yang Anda lakukan, Anda mungkin bisa melakukannya dengan membalik loop untuk mengulang ke belakang.

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

1
Ini adalah kesalahan untuk menelepon strlensama sekali. Ulangi sampai Anda mencapai akhir.
R .. GitHub STOP HELPING ICE

i > 0? Bukankah seharusnya itu ada di i >= 0sini? Secara pribadi, saya juga akan mulai strlen(s) - 1jika mengulangi string ke belakang, maka pengakhiran \0tidak memerlukan pertimbangan khusus.
CVn

2
@ MichaelKjörling i >= 0hanya berfungsi jika Anda menginisialisasi ke strlen(s) - 1, tetapi kemudian jika Anda memiliki string dengan panjang nol, nilai awal kurang mengalir
Praetorian

@ Prætorian, poin bagus pada string panjang nol. Saya tidak mempertimbangkan kasus itu ketika saya menulis komentar saya. Apakah C ++ mengevaluasi i > 0ekspresi pada entri pengulangan awal? Jika tidak, maka Anda benar, case dengan panjang nol pasti akan memutuskan loop. Jika ya, Anda "cukup" mendapatkan tanda i== -1 <0 sehingga tidak ada entri loop jika kondisionalnya i >= 0.
CVn

@ MichaelKjörling Ya, kondisi keluar dievaluasi sebelum menjalankan loop untuk pertama kalinya. strlenJenis kembalian tidak bertanda tangan, sehingga (strlen(s)-1) >= 0mengevaluasi true untuk string panjang nol.
Praetorian

3

Secara formal ya, strlen()diharapkan dipanggil untuk setiap iterasi.

Bagaimanapun saya tidak ingin meniadakan kemungkinan adanya beberapa optimasi kompilator pintar, yang akan mengoptimalkan panggilan berurutan ke strlen () setelah yang pertama.


3

Kode predikat secara keseluruhan akan dieksekusi pada setiap iterasi forloop. Untuk mengingat hasil strlen(ss)panggilan, kompilator perlu mengetahui hal itu setidaknya

  1. Fungsinya strlenbebas efek samping
  2. Memori yang ditunjukkan oleh sstidak berubah selama durasi loop

Kompilator tidak mengetahui salah satu dari hal-hal ini dan karenanya tidak dapat dengan aman membuat memo hasil panggilan pertama


Ia bisa mengetahui hal-hal tersebut dengan analisis statis, tapi saya pikir maksud Anda adalah bahwa analisis tersebut saat ini tidak diterapkan di kompiler C ++, ya?
GManNickG

@GManNickG pasti bisa membuktikan # 1 tetapi # 2 lebih sulit. Untuk satu utas ya itu pasti bisa membuktikannya tetapi tidak untuk lingkungan multi-utas.
JaredPar

1
Mungkin saya keras kepala tapi saya pikir nomor dua juga mungkin dalam lingkungan multithread, tapi jelas bukan tanpa sistem inferensi yang sangat kuat. Hanya merenung di sini; pasti di luar cakupan kompiler C ++ saat ini.
GManNickG

@GManNickG saya rasa tidak mungkin meskipun dalam C / C ++. Saya dapat dengan mudah menyimpan alamat sske dalam size_tatau membaginya di antara beberapa bytenilai. Utas licik saya kemudian bisa saja menulis byte ke alamat itu dan kompiler akan tahu cara memahami yang terkait dengannya ss.
JaredPar

1
@JaredPar: Maaf bang, Anda dapat mengklaim itu int a = 0; do_something(); printf("%d",a);tidak dapat dioptimalkan, atas dasar yang do_something()dapat melakukan hal int yang tidak diinisialisasi, atau dapat merayapi kembali tumpukan dan memodifikasi dengan asengaja. Faktanya, gcc 4.5 mengoptimalkannya do_something(); printf("%d",0);dengan -O3
Steve Jessop

2

Iya . strlen akan dihitung setiap kali saya meningkat.

Jika Anda tidak mengubah ss dengan in loop berarti itu tidak akan mempengaruhi logika jika tidak maka akan mempengaruhi.

Lebih aman menggunakan kode berikut.

int length = strlen(ss);

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

2

Ya, perusahaan strlen(ss)akan menghitung panjang pada setiap iterasi. Jika Anda meningkatkan ssdengan cara tertentu dan juga meningkatkan i; akan ada putaran tak terbatas.


2

Ya, strlen()fungsinya dipanggil setiap kali loop dievaluasi.

Jika Anda ingin meningkatkan efisiensi maka selalu ingat untuk menyimpan semuanya di variabel lokal ... Ini akan memakan waktu tetapi sangat berguna ..

Anda dapat menggunakan kode seperti di bawah ini:

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

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


2

Tidak umum saat ini tetapi 20 tahun yang lalu pada platform 16 bit, saya merekomendasikan ini:

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

Bahkan jika kompiler Anda tidak terlalu pintar dalam pengoptimalan, kode di atas dapat menghasilkan kode assembly yang bagus.


1

Iya. Pengujian tidak mengetahui bahwa ss tidak berubah di dalam loop. Jika Anda tahu bahwa itu tidak akan berubah maka saya akan menulis:

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

1

Ah, itu akan, bahkan dalam keadaan ideal, sialan!

Mulai hari ini (Januari 2018), dan gcc 7.3 dan clang 5.0, jika Anda mengompilasi:

#include <string.h>

void bar(char c);

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

Jadi kita punya:

  • ss adalah penunjuk konstan.
  • ss ditandai __restrict__
  • Badan loop tidak dapat menyentuh memori yang ditunjukkan oleh ss(yah, kecuali jika melanggar __restrict__).

dan tetap saja , kedua kompiler mengeksekusi strlen() setiap iterasi loop tersebut . Luar biasa.

Ini juga berarti kiasan / angan-angan dari @Praetorian dan @JaredPar tidak berjalan dengan baik.


0

YA, dengan kata sederhana. Dan ada sedikit kata tidak dalam kondisi langka di mana kompilator menginginkannya, sebagai langkah pengoptimalan jika ternyata tidak ada perubahan sssama sekali. Tetapi dalam kondisi aman Anda harus menganggapnya YA. Ada beberapa situasi seperti in multithreadeddan event driven program, ini mungkin bermasalah jika Anda menganggapnya TIDAK. Lakukan dengan aman karena tidak akan terlalu meningkatkan kompleksitas program.


0

Iya.

strlen()dihitung setiap saat imeningkat dan tidak dioptimalkan.

Kode di bawah ini menunjukkan mengapa kompilator tidak harus dioptimalkan strlen().

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

Saya pikir melakukan modifikasi khusus itu tidak pernah mengubah panjang ss, hanya isinya, jadi kompiler (yang benar-benar pintar) masih bisa mengoptimalkan strlen.
Darren Cook

0

Kami dapat dengan mudah mengujinya:

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

Kondisi loop mengevaluasi setelah setiap pengulangan, sebelum mengulang loop.

Juga berhati-hatilah dengan jenis yang Anda gunakan untuk menangani panjang string. itu harus size_tyang telah didefinisikan seperti unsigned intdi stdio. membandingkan dan mentransmisikannya intmungkin menyebabkan beberapa masalah kerentanan yang serius.


0

well, saya perhatikan bahwa seseorang mengatakan bahwa ini dioptimalkan secara default oleh kompiler modern "pintar". Dengan cara melihat hasil tanpa pengoptimalan. Saya mencoba:
Kode C Minimal:

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

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

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

Kompiler saya: g ++ (Ubuntu / Linaro 4.6.3-1ubuntu5) 4.6.3
Perintah untuk pembuatan kode 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
     .....

Saya akan enggan untuk mempercayai kompiler yang mencoba mengoptimalkannya kecuali alamat stringnya restrict -qualified. Meskipun ada beberapa kasus di mana pengoptimalan seperti itu akan sah, upaya yang diperlukan untuk mengidentifikasi kasus seperti itu secara andal tanpa adanya restrictkemauan, dengan ukuran yang wajar, hampir pasti melebihi manfaatnya. Jika alamat string memiliki const restrictkualifikasi, bagaimanapun, itu akan cukup untuk membenarkan pengoptimalan tanpa harus melihat yang lain.
supercat

0

Menguraikan jawaban Prætorian, saya merekomendasikan yang berikut ini:

for( auto i = strlen(s)-1; i > 0; --i ) {foo(s[i-1];}
  • autokarena Anda tidak ingin peduli tentang tipe mana yang dikembalikan strlen. Kompiler C ++ 11 (misalnya gcc -std=c++0x, tidak sepenuhnya C ++ 11 tetapi tipe otomatis berfungsi) akan melakukannya untuk Anda.
  • i = strlen(s)karena Anda ingin membandingkan 0(lihat di bawah)
  • i > 0 karena perbandingan dengan 0 (sedikit) lebih cepat dibandingkan dengan angka lainnya.

kelemahannya adalah Anda harus menggunakan i-1untuk mengakses karakter string.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.