Boş sonlandırılmış dizelerin mantığı nedir?


281

C ve C ++ 'ı sevdiğim kadarıyla, boş sonlandırılmış dizelerin seçiminde başımı çizemiyorum:

  • Önceden eklenmiş uzunluk (yani Pascal) dizeleri C'den önce vardı
  • Uzunluk ön ekli dizeler, sabit süre uzunluğu aramasına izin vererek birkaç algoritmayı daha hızlı hale getirir.
  • Uzunluk ön ekli dizeler, arabellek taşması hatalarına neden olmayı zorlaştırır.
  • 32 bitlik bir makinede bile, dizenin kullanılabilir belleğin boyutu olmasına izin verirseniz, uzunluk ön ekli dize, boş bir sonlandırılmış dizeden yalnızca üç bayt daha geniştir. 16 bitlik makinelerde bu tek bir bayttır. 64 bitlik makinelerde, 4GB makul bir dize uzunluğu sınırıdır, ancak makine sözcüğünün boyutuna genişletmek isteseniz bile, 64 bitlik makineler genellikle fazladan yedi baytlık null argüman yapan geniş bir belleğe sahiptir. Orijinal C standardının inanılmaz derecede zayıf makineler için (bellek açısından) yazıldığını biliyorum, ancak verimlilik argümanı beni burada satmıyor.
  • Diğer tüm diller (ör. Perl, Pascal, Python, Java, C #, vb.) Uzunluk ön ekli dizeler kullanır. Bu diller genellikle string manipülasyon kriterlerinde C'yi geçerler çünkü stringlerle daha verimlidirler.
  • C ++ bunu std::basic_stringşablonla biraz düzeltti , ancak boş sonlandırılmış dizeler bekleyen düz karakter dizileri hala yaygın. Bu aynı zamanda kusurludur, çünkü yığın tahsisi gerektirir.
  • Boş sonlandırılmış dizeler, dizede bulunmayan bir karakter (yani null) ayırmalıdır; uzunluk ön ekli dizeler katıştırılmış boş değerler içerebilir.

Bunların birçoğu C'den daha yakın bir zamanda ortaya çıktı, bu yüzden C'nin onları bilmemesi mantıklı olurdu. Ancak, birkaç kişi C gelmeden çok açıktı. Açıkça üstün uzunluklu önek yerine null sonlandırılmış dizeler neden seçilsin ki?

EDIT : Bazıları yukarıdaki verimlilik noktamda gerçekler istediğinden (ve zaten sağladığımları beğenmediğinden), birkaç şeyden kaynaklanıyor:

  • Boş sonlandırılmış dizeler kullanılarak yapılan concat, O (n + m) zaman karmaşıklığı gerektirir. Uzunluk ön eki genellikle yalnızca O (m) gerektirir.
  • Boş sonlandırılmış dizeler kullanan uzunluk O (n) zaman karmaşıklığı gerektirir. Uzunluk ön eki O (1) 'dir.
  • Uzunluk ve concat açık ara en yaygın dize işlemleri. Boş sonlandırılmış dizelerin daha verimli olabileceği birkaç durum vardır, ancak bunlar çok daha az sıklıkla görülür.

Aşağıdaki yanıtlardan, boş sonlandırılmış dizelerin daha verimli olduğu bazı durumlar şunlardır:

  • Bir dizenin başlangıcını kesmeniz ve bir yönteme geçirmeniz gerektiğinde. Orijinal dizeyi yok etmenize izin verilse bile, bunu uzunluk önekiyle sabit bir zamanda yapamazsınız, çünkü uzunluk önekinin hizalama kurallarına uyması gerekir.
  • Bazı karakterleri karakter dizisine göre döngüye soktuğunuz durumlarda, bir CPU kaydını kaydedebilirsiniz. Bunun yalnızca dizeyi dinamik olarak ayırmamış olmanız durumunda işe yaradığını unutmayın (Çünkü o zaman serbest bırakmak zorunda kalacaksınız, çünkü malloc ve arkadaşlardan aldığınız işaretçiyi tutmak için kaydettiğiniz CPU kaydını kullanmanız gerekir).

Yukarıdakilerin hiçbiri uzunluk ve akrabalık kadar yaygın değildir.

Aşağıdaki cevaplarda bir iddia daha var:

  • Dizenin sonunu kesmelisin

ancak bu yanlıştır - boş sonlandırılmış ve uzunluk ön ekli dizeler için aynı süre geçerlidir. (Boş sonlandırılmış dizeler, yeni sonun olmasını istediğiniz yere boş bir değer yapıştırır; uzunluk önekleri yalnızca önekten çıkarılır.)


110
Her zaman tüm C ++ programcıları kendi dize kitaplığı yazmak için bir geçit ayin olduğunu düşündüm.
Juliet

31
Şimdi rasyonel açıklamalar beklemenin anlamı nedir. Sanırım sonraki x86 veya DOS için bir mantık duymak isteyeceksiniz? Bence en kötü teknoloji kazanıyor. Her zaman. Ve en kötü dize gösterimi.
jalf

4
Neden uzunluk önek dizelerinin üstün olduğunu iddia ediyorsunuz? Sonuçta, C popüler oldu, çünkü diğer dillerden ayıran boş sonlandırılmış dizeler kullandı.
Daniel C.Sobral

44
@Daniel: C, Von Neumann makinelerinde çalıştırılabilen programların basit, verimli ve taşınabilir bir temsili olduğu ve Unix için kullanıldığı için popüler oldu. Kesinlikle boş sonlandırılmış dizeler kullanmaya karar verdiği için değil. İyi bir tasarım kararı olsaydı, insanlar bunu kopyalardı ve yapmazlardı. Kesinlikle C'den hemen hemen her şeyi kopyaladılar
Billy ONeal

4
Eğer dizelerden birini yok ederseniz Concat sadece O (m) uzunluk ön ekidir. Aksi takdirde, aynı hız. C dizelerinin (tarihsel olarak) en yaygın kullanımları baskı ve taramadır. Her ikisinde de boş sonlandırma daha hızlıdır çünkü bir kayıt kaydeder.
Daniel C.Sobral

Yanıtlar:


195

Gönderen at ağzına

Hiçbir BCPL, B veya C, karakter verilerini dilde güçlü bir şekilde desteklemez; her biri dizelere tamsayı vektörleri gibi davranır ve genel kuralları birkaç kuralla tamamlar. BCPL ve B'de bir dize değişmez değeri, dizeye karakterlerle başlatılan ve hücrelere paketlenmiş statik bir alanın adresini gösterir. BCPL'de, ilk paketlenmiş bayt dizede karakter sayısını içerir; B'de sayım yoktur ve dizeler B'nin yazdığı özel bir karakterle sonlandırılır *e. Bu değişiklik, sayının 8 veya 9 bitlik bir yuvada tutulmasının neden olduğu bir dizenin uzunluğundaki sınırlamadan kaçınmak için kısmen yapıldı ve kısmen sayımın tutulması, deneyimlerimize göre, bir sonlandırıcı kullanmaktan daha az uygun görünüyordu.

Dennis M Ritchie, C Dilinin Gelişimi


12
Başka bir ilgili alıntı: "... dizelerin anlambilimi, tüm dizileri yöneten daha genel kurallarla tamamen ele geçirilir ve sonuç olarak dilin tanımlanması daha kolaydır ..."
AShelly

151

C'nin dilin bir parçası olarak bir dizesi yoktur. C'deki bir 'dize' sadece karakter için bir göstergedir. Belki de yanlış soruyu soruyorsun.

"Bir dize türünü dışarıda bırakmanın mantığı nedir" daha alakalı olabilir. Bunun için C'nin nesneye yönelik bir dil olmadığını ve sadece temel değer türlerine sahip olduğunu belirtmek isterim. Dize, bir şekilde diğer türlerin değerlerini birleştirerek uygulanması gereken daha üst düzey bir kavramdır. C daha düşük bir soyutlama seviyesindedir.

aşağıdaki şiddetli fırtına ışığında:

Ben sadece bu aptalca ya da kötü bir soru olduğunu söylemeye çalışmıyorum ya da dizeleri temsil C yolu en iyi seçim olduğunu belirtmek istiyorum. C bir bayt diziden bir veri türü olarak bir dize ayırt etmek için bir mekanizma yok aslında dikkate alırsanız soru daha kısa sürede koymak olacağını açıklamaya çalışıyorum. Günümüz bilgisayarlarının işlem ve bellek gücü ışığında bu en iyi seçim mi? Muhtemelen değil. Ama gez her zaman 20/20 ve tüm bunlar :)


29
char *temp = "foo bar";C geçerli bir ifadedir ... hey! bu bir dize değil mi? boş bırakılmadı mı?
Yanick Rochon

56
@Yanick: Bu derleyiciye sonunda boş bir karakter dizisi oluşturmasını söylemenin kullanışlı bir yoludur. bu bir 'string' değil
Robert S Ciaccio

28
@calavera: Ama bu basitçe "Bu dize içeriği ve iki bayt uzunluk öneki ile bir bellek tamponu oluştur" anlamına
gelebilirdi

14
@Billy: bir 'dize' gerçekten sadece char için bir işaretçi olduğundan, bayt için bir işaretçiye eşdeğer olduğundan, uğraştığınız ara belleğin gerçekten bir 'dize' olması amaçlandığını nasıl bilebilirsiniz? bunu belirtmek için char / byte * dışında yeni bir türe ihtiyacınız olacaktır. belki bir yapı?
Robert S Ciaccio

27
Bence @calavera doğru, C dizeleri için bir veri türü yok. Tamam, dize gibi karakter dizisini düşünebilirsiniz, ancak bu her zaman bir dize anlamına gelmez (dize için belirli bir anlamı olan bir karakter dizisi anlamına gelir). İkili dosya bir dizi karakterdir, ancak bu karakterlerin bir insan için bir anlamı yoktur.
BlackBear

106

Soru bir Length Prefixed Strings (LPS)vs sorusu olarak sorulur zero terminated strings (SZ), ancak çoğunlukla uzunluk ön ekli dizelerin faydalarını ortaya çıkarır. Bu ezici görünebilir, ancak dürüst olmak gerekirse, LPS'nin dezavantajlarını ve SZ'nin avantajlarını da düşünmeliyiz.

Anladığım kadarıyla, soru "Sıfır Sonlandırılmış Dizelerin avantajları nelerdir?" Sorusunun taraflı bir yolu olarak bile anlaşılabilir.

Sıfır Sonlandırılmış Dizelerin Avantajları (anlıyorum):

  • çok basit, dilde yeni kavramlar tanıtmaya gerek yok, char dizileri / char işaretçiler yapabilirsiniz.
  • Çekirdek dil, çift tırnaklar arasındaki bir şeyi bir sürü karaktere (gerçekten bir demet bayt) dönüştürmek için minimal sözdizimi şekeri içerir. Bazı durumlarda, metinle tamamen ilgisiz olan şeyleri başlatmak için kullanılabilir. Örneğin, xpm görüntü dosyası biçimi, dize olarak kodlanan görüntü verilerini içeren geçerli bir C kaynağıdır.
  • bu arada, yapabilirsiniz derleyici sadece ayrıca edebi sonunda bir tane daha ekleyecek bir dize bir sıfır koyun: "this\0is\0valid\0C". Bu bir dize mi? veya dört tel mi? Ya da bir demet bayt ...
  • düz uygulama, gizli dolaylı, gizli tamsayı yok.
  • hiçbir gizli bellek tahsisi söz konusu değildir (iyi, strdup gibi bazı standart dışı işlevler tahsis gerçekleştirir, ancak bu çoğunlukla bir sorun kaynağıdır).
  • küçük veya büyük donanım için belirli bir sorun yok (8 bit mikrodenetleyicilerde 32 bit önek uzunluğunu yönetme yükünü veya dize boyutunu 256 bayttan daha azıyla sınırlama sınırlarını hayal edin, bu aslında Turbo Pascal eons ile ilgili bir sorunumdu).
  • dize manipülasyonunun uygulanması sadece bir avuç çok basit kütüphane fonksiyonudur
  • dizelerin ana kullanımı için verimli: bilinen bir başlangıçtan itibaren sıralı olarak okunan sabit metin (çoğunlukla kullanıcıya mesajlar).
  • sonlandırma sıfır bile zorunlu değildir, bir dizi bayt gibi karakterleri işlemek için gerekli tüm araçlar mevcuttur. C'de dizi başlatma işlemini gerçekleştirirken, NUL sonlandırıcıdan bile kaçınabilirsiniz. Sadece doğru boyutu ayarlayın. char a[3] = "foo";geçerli C'dir (C ++ değil) ve a'ya son sıfır koymaz.
  • stdin, stdout gibi içsel uzunluğu olmayan "dosyalar" dahil olmak üzere "her şey dosyadır" unix bakış açısıyla uyumludur. Açık okuma ve yazma ilkellerinin çok düşük bir seviyede uygulandığını unutmamalısınız. Bunlar kütüphane çağrıları değil, sistem çağrılarıdır. İkili veya metin dosyaları için de aynı API kullanılır. Dosya okuma ilkeleri bir arabellek adresi ve bir boyut alır ve yeni boyutu döndürür. Ve yazmak için arabellek olarak dizeleri kullanabilirsiniz. Başka tür bir dize temsili kullanmak, basit bir dizgeyi çıktı olarak tampon olarak kolayca kullanamayacağınız anlamına gelir veya bunu yayınlarken çok garip bir davranışa sahip olmanız gerekir char*. Yani dizenin adresini döndürmek yerine gerçek verileri döndürmek.
  • bir dosyadan okunan metin verilerini, arabellek yararsız bir kopyası olmadan, değiştirmek için çok kolay, sadece doğru yerlere sıfır ekleyin (çift tırnaklı dizeler günümüzde genellikle değiştirilemez verilerde tutulan sabit diziler olduğundan, gerçekten modern C ile değil kesim).
  • hangi boyutta olursa olsun bazı int değerlerinin eklenmesi, hizalama sorunları anlamına gelir. Başlangıç ​​uzunluğu hizalanmalıdır, ancak karakter verileri için bunu yapmak için hiçbir neden yoktur (ve yine, dizelerin hizalanmasını zorlamak, bir demet bayt olarak tedavi edilirken sorun yaratacaktır).
  • uzunluk, sabit değişmez dizeler için derleme zamanında bilinir (sizeof). Öyleyse neden gerçek verilerden önce bellekte saklamak istesin ki?
  • C (neredeyse) herkes gibi yapıyorsa, dizeler karakter dizisi olarak görülür. Dizi uzunluğu C tarafından yönetilmediğinden, mantıksal uzunluk da dizeler için yönetilmez. Tek şaşırtıcı olan şey, 0 öğenin sonuna eklenmiş olmasıdır, ancak bu, çift tırnak arasına bir dize yazarken temel dil düzeyindedir. Kullanıcılar, uzunluğu geçen dize düzenleme işlevlerini mükemmel bir şekilde çağırabilir veya bunun yerine düz memcopy kullanabilirler. SZ sadece bir tesistir. Diğer birçok dilde dizi uzunluğu yönetilir, dizeler için aynı olan mantıklıdır.
  • yine de modern zamanlarda 1 bayt karakter setleri yeterli değildir ve genellikle karakter sayısının bayt sayısından çok farklı olduğu kodlanmış unicode dizelerle uğraşmak zorunda kalırsınız. Bu, kullanıcıların büyük olasılıkla "yalnızca boyuttan" daha fazlasını ve aynı zamanda diğer bilgileri isteyeceğini ima eder. Uzunluğu korumak, bu diğer faydalı bilgilerle ilgili hiçbir şey kullanmaz (özellikle saklamak için doğal bir yer yoktur).

Bununla birlikte, standart C dizelerinin gerçekten verimsiz olduğu nadir durumlarda şikayet etmeye gerek yoktur. Lib'ler mevcuttur. Bu eğilimi izlesem, standart C'nin herhangi bir normal ifade destek işlevi içermediğinden şikayet etmeliyim ... ama gerçekten herkes bunun gerçek bir sorun olmadığını biliyor, çünkü bu amaç için kullanılabilir kütüphaneler var. Dize düzenleme verimliliği istendiğinde neden bstring gibi bir kitaplık kullanılmıyor ? Ya da C ++ dizeleri?

EDIT : Geçenlerde D dizeleri bir göz vardı . Seçilen çözümün ne boyut öneki ne de sıfır sonlandırma olduğunu görmek yeterince ilginçtir. C'de olduğu gibi, çift tırnak içine alınmış değişmez dizgiler değişmez karakter dizileri için kısa eldir ve dil aynı zamanda (değişmez karakter dizisi) anlamına gelen bir dize anahtar sözcüğüne sahiptir.

Ancak D dizileri C dizilerinden çok daha zengindir. Statik dizilerde, çalışma zamanında uzunluk bilinmektedir, bu nedenle uzunluğun depolanmasına gerek yoktur. Derleyici derleme zamanında vardır. Dinamik diziler söz konusu olduğunda uzunluk mevcuttur ancak D dokümantasyonu nerede tutulduğunu belirtmez. Tüm bildiğimiz için, derleyici bunu bir kayıtta veya karakter verilerinden uzakta saklanan bazı değişkenlerde tutmayı seçebilir.

Normal karakter dizilerinde veya değişmez dizgilerde son sıfır yoktur, bu nedenle programcı D'den biraz C işlevi çağırmak istiyorsa kendisini koymalıdır. Özel dizgi dizilerinde D derleyicisi hala Her bir dizenin sonu (C işlevlerini çağırmayı kolaylaştırmak için C dizelerine kolay yayın yapılmasına izin vermek için?), ancak bu sıfır dizenin bir parçası değildir (D dizgi boyutunda saymaz).

Beni biraz hayal kırıklığına uğratan tek şey, dizelerin utf-8 olması gerekiyordu, ancak uzunluk çok baytlık karakterleri kullanırken bile hala bir dizi bayt (en azından derleyici gdc'de doğrudur) döndürüyor. Bir derleyici hatası mı yoksa amaç için mi olduğu belli değil. (Tamam, muhtemelen ne olduğunu öğrendim. D derleyicinize kaynağınızı utf-8 kullanmak için başlangıçta aptal bayt sırası işareti koymak zorundasınız. Aptal yazıyorum, özellikle editör olmadığını biliyorum, özellikle UTF- ASCII uyumlu olması gerekir).


7
... Devamı ... Sanırım birkaç noktanız basitçe yanlış, yani "her şey bir dosya" argümanı. Dosyalar sıralı erişimdir, C dizeleri değildir. Uzunluk ön eki, minimal sözdizimsel şekerle de yapılabilir. Buradaki tek makul argüman, küçük (yani 8 bit) donanımda 32 bit önekleri yönetmeye çalışmaktır; Bence bu uzunluk büyüklüğünün uygulama tarafından belirlendiğini söyleyerek çözülebilir. Sonuçta, bu std::basic_stringböyle.
Billy ONeal

3
@Billy ONeal: cevabımda gerçekten iki farklı bölüm var. Biri 'çekirdek C dilinin' bir parçası, diğeri standart kütüphanelerin ne sunması gerektiğidir. Dize desteğiyle ilgili olarak , temel dilden sadece bir öğe vardır : çift tırnaklı kapalı bayt demetinin anlamı. C davranışı ile senden gerçekten mutlu değilim. Büyülü bir şekilde eklenen her baytın sonuna sıfır eklenmiş bayt demetinin yeterince kötü olduğunu hissediyorum. \0Programcılar örtük yerine bunu istediklerinde sonunda tercih ederdim . Hazırlanan uzunluk çok daha kötü.
kriss

2
@Billy ONeal: bu doğru değil, kullanımlar neyin çekirdek ve kütüphanelerin ne olduğunu önemsiyor. En büyük nokta, OS'yi uygulamak için C kullanıldığındadır. Bu seviyede hiçbir kütüphane mevcut değildir. C genellikle gömülü bağlamlarda veya genellikle aynı tür kısıtlamalara sahip olduğunuz programlama aygıtlarında kullanılır. Çoğu durumda Joes'in muhtemelen C'yi bugün hiç kullanmaması gerekir: "Tamam, konsolda istiyor musunuz? Bir konsolunuz var mı? Hayır? Çok kötü ..."
kriss

5
@Billy "Eh, işletim sistemleri uygulayan C programcılarının% .01'i için para cezası." Diğer programcılar yürüyüş yapabilir. C bir işletim sistemi yazmak için oluşturuldu.
Daniel C.Sobral

5
Neden? Çünkü bu genel amaçlı bir dil mi diyor? Yazan insanların yarattıklarında ne yaptığını söylüyor mu? Hayatının ilk birkaç yılında ne kullanıldı? Peki, benimle aynı fikirde olmadığını söyleyen nedir? Bir işletim sistemi yazmak için oluşturulan genel amaçlı bir dildir . Reddediyor mu?
Daniel C.Sobral

61

Bence, tarihsel nedenleri var ve bunu wikipedia'da buldu :

C (ve türetildiği diller) geliştirilirken, bellek son derece sınırlıydı, bu nedenle bir dizenin uzunluğunu saklamak için sadece bir bayt ek yükü kullanmak çekici idi. O zamanlar genellikle "Pascal dizesi" olarak adlandırılan tek popüler alternatif (BASIC'in ilk sürümlerinde de kullanılsa da) dizenin uzunluğunu depolamak için önde gelen bir bayt kullandı. Bu, dizginin NUL içermesini sağlar ve uzunluğu bulmak için yalnızca bir bellek erişimine (O (1) (sabit) zaman) ihtiyaç duyulur. Ancak bir bayt uzunluğu 255 ile sınırlar. Bu uzunluk sınırlaması C dizesiyle ilgili problemlerden çok daha kısıtlayıcıydı, bu nedenle C dizisi genel olarak kazandı.


2
@muntoo Hmm ... uyumluluğu?
khachik

19
@muntoo: Çünkü bu, mevcut C ve C ++ kodlarının anıtsal miktarlarını kırabilir.
Billy ONeal

10
@muntoo: Paradigmalar gelir ve gider, ancak eski kod sonsuza dek kalır. C'nin gelecekteki herhangi bir sürümünün 0 sonlandırılmış dizeleri desteklemeye devam etmesi gerekir, aksi takdirde 30 yıldan fazla eski kodun yeniden yazılması gerekir (bu gerçekleşmez). Ve eski yol mevcut olduğu sürece, insanlar kullanmaya devam edeceklerdir, çünkü aşina oldukları şey budur.
John Bode

8
@muntoo: İnan bana, bazen keşke yapabilseydim. Ama yine de Pascal dizgileri yerine 0 sonlu dizeleri tercih ederim.
John Bode

2
Eski hakkında konuşun ... C ++ dizeleri artık NUL ile sonlandırıldı.
Jim Balter

32

Calavera olduğu doğru , ama insanlar onun noktası olsun görünmemektedir gibi, bazı kod örnekler sunacağız.

İlk olarak, C'nin ne olduğunu düşünelim: tüm kodların makine diline oldukça doğrudan bir çevirisi olan basit bir dil. Tüm türler kayıtlara ve yığına sığar ve çalıştırmak için bir işletim sistemi veya büyük bir çalışma zamanı kütüphanesi gerektirmez, çünkü bu şeyleri yazmak için tasarlanmıştır (orada dikkate alınarak son derece uygun bir görev) bu gün için muhtemel bir rakip bile değil).

C'nin veya stringgibi bir türü olsaydı, bir kayıt defterine veya yığına sığmayan ve herhangi bir şekilde ele alınması için bellek tahsisinin (tüm destekleyici altyapısı ile) yapılmasını gerektiren bir tür olurdu. Hepsi C'nin temel prensiplerine aykırıdır.intchar

Yani, C'deki bir dize:

char s*;

Öyleyse, bunun uzunluğunda önek olduğunu varsayalım. İki dizeyi birleştirmek için kodu yazalım:

char* concat(char* s1, char* s2)
{
    /* What? What is the type of the length of the string? */
    int l1 = *(int*) s1;
    /* How much? How much must I skip? */
    char *s1s = s1 + sizeof(int);
    int l2 = *(int*) s2;
    char *s2s = s2 + sizeof(int);
    int l3 = l1 + l2;
    char *s3 = (char*) malloc(l3 + sizeof(int));
    char *s3s = s3 + sizeof(int);
    memcpy(s3s, s1s, l1);
    memcpy(s3s + l1, s2s, l2);
    *(int*) s3 = l3;
    return s3;
}

Başka bir alternatif, bir dizeyi tanımlamak için bir yapı kullanmaktır:

struct {
  int len; /* cannot be left implementation-defined */
  char* buf;
}

Bu noktada, tüm dize manipülasyonu iki tahsisat gerektirir, bu da pratikte, herhangi bir işlem yapmak için bir kütüphaneden geçeceğiniz anlamına gelir.

Garip olan ... gibi yapılar yapmak C var! Bunlar, günlük kullanımınız için kullanıcı yönetimine yönelik mesajları görüntülemek için kullanılmaz.

İşte Calavera'nın yaptığı şey: C'de dize türü yok . Onunla herhangi bir şey yapmak için, bir işaretçi alıp iki farklı türe işaretçi olarak kodunu çözmeniz gerekir ve sonra bir dizenin boyutunun ne olduğu çok alakalı olur ve "uygulama tanımlı" olarak bırakılamaz.

Şimdi, C olabilir yine de hafıza ve kolu mem(kitaplıktaki işlevleri <string.h>! Bile,) sen pointer ve boyutta bir çifti olarak sap belleğine gereken tüm takım sağlarlar. Sözde "dizeleri" metin terminalleri için amaçlanan bir işletim sistemi yazımı bağlamında gösteren mesajlar: C sadece bir amaç için oluşturulmuştur. Ve bunun için boş sonlandırma yeterlidir.


2
1. +1. 2. Açıkça, dilin varsayılan davranışı uzunluk önekleri kullanılarak yapılmış olsaydı, bunu kolaylaştırmak için başka şeyler de olurdu. Örneğin, oradaki tüm yayınlarınız aramalar strlenve arkadaşlar tarafından gizlenmiş olurdu . "Uygulamaya bırakma" sorununa gelince, önekin shorthedef kutuda a'nın ne olduğunu söyleyebilirsiniz . Sonra tüm dökümleriniz hala işe yarayacaktı. 3. Gün boyunca bir veya diğer sistemin kötü görünmesini sağlayan anlaşmalı senaryolar bulabilirim.
Billy ONeal

5
@Billy Kütüphane olayı, C'nin kütüphane kullanımı için minimal veya hiç kullanılmayacak şekilde tasarlanması dışında yeterince doğrudur. Örneğin, prototiplerin kullanımı erken yaygın değildi. Önekin shortetkili olduğunu söylemek, dizenin boyutunu sınırlar, bu da merak edilmedikleri bir şey gibi görünüyor. Kendim, 8 bitlik BASIC ve Pascal dizeleri, sabit boyutlu COBOL dizeleri ve benzeri şeylerle çalışarak, hızlı bir şekilde sınırsız boyutlu C dizelerinin büyük bir hayranı oldum. Günümüzde, 32 bit büyüklüğünde herhangi bir pratik dize işlenecek, ancak bu baytların erken eklenmesi sorunluydu.
Daniel C.Sobral

1
@Billy: Öncelikle, teşekkür ederim Daniel ... Neler yaptığımı anlıyor gibisin. İkincisi, Billy, sanırım burada yapılan noktayı hala özlüyorsun. Ben biri için uzunluğu ile dize veri türlerini önek artılarını ve eksilerini tartışıyor değilim . Söylemek ve ne Daniel çok net vurguladı sürerek iddiayı işlemez C uygulanmasında kaydedilen bir karar olmasıdır am hiç . Temel dil söz konusu olduğunda dizeler mevcut değildir. Dizelerin nasıl ele alınacağına dair karar programcıya bırakılır ... ve boş sonlandırma popüler hale gelir.
Robert S Ciaccio

1
+ 1'ledim. Eklemek istediğim bir şey daha; önerdiğiniz gibi bir yapı, gerçek bir stringtüre doğru önemli bir adımı atlıyor : karakterlerin farkında değil. Bu "char" dizisidir (makine lingo'daki bir "char", "cümle" olarak adlandırılan "kelime" olduğu kadar karakterdir). Bir karakter dizisi , kodlama kavramını tanıttıysanız, bir dizinin üzerine uygulanabilecek daha yüksek düzeyli bir kavramdır char.
Frerich Raabe

2
@ DanielC.Sobral: Ayrıca, bahsettiğiniz yapı iki tahsis gerektirmez. Yığında olduğu gibi kullanın (bu nedenle yalnızca bufbir ayırma gerektirir) veya struct string {int len; char buf[]};her şeyi esnek bir dizi üyesi olarak tek bir ayırma ile kullanın ve ayırın ve bir string*. (Veya tartışmalı struct string {int capacity; int len; char buf[]};performans nedenleriyle)
Mooing Duck

20

Açıkçası performans ve güvenlik için, tekrar tekrar performans yapmak strlenveya üzerinde eşdeğer olmak yerine, bir dizenin uzunluğunu korumak isteyeceksiniz . Bununla birlikte, uzunluğu dize içeriğinden hemen önce sabit bir yerde saklamak inanılmaz derecede kötü bir tasarımdır. Jörgen'in Sanjit'in cevabındaki yorumlarda işaret ettiği gibi, bir dizginin kuyruğuna bir dize gibi davranmayı engeller, bu da örneğin yeni bellek ayırmadan (ve hata ve hata işleme olasılığına maruz kalmadan) birçok yaygın işlemi imkansız path_to_filenameveya filename_to_extensionimkansız hale getirir. . Ve sonra elbette kimse dize uzunluğu alanının kaç bayt işgal etmesi gerektiği konusunda anlaşamaz (çok sayıda kötü "Pascal dizesi"

C'nin, programlayıcının uzunluğun daha esnek / güçlü olup olmadığını / nerede / nasıl saklanacağını seçmesine izin verme tasarımı. Ama elbette programcı akıllı olmalı. C, çökme, durma noktasına gelme veya düşmanlarınıza kök salma programları ile aptallığı cezalandırır.


+1. Uzunluğu önek gibi bir şey isteyenlerin tonlarca "tutkal kodu" yazmak zorunda kalmamak için uzunluğu saklamak için standart bir yere sahip olmak güzel olurdu.
Billy ONeal

2
Dize verilerine göre olası bir standart yer yoktur, ancak elbette ayrı bir yerel değişken (ikincisi uygun olmadığında ve eski çok israflı olmadığında geçmek yerine yeniden hesaplamak) veya bir işaretçi içeren bir yapı kullanabilirsiniz. dizeye (ve hatta daha iyisi, yapının ayırma amacıyla işaretçiye "sahip olup olmadığını" veya başka bir yerde sahip olunan bir dizeye başvuru olup olmadığını gösteren bir bayrak. size uygun olduğunda yapıyı içeren dize
.. .. GitHub DURDURMA BUZA YARDIM

13

Tembellik, herhangi bir dilin, özellikle montajın bir adım üzerinde olan C'nin montaj bağırsaklarını göz önünde bulundurarak tutumluluk ve taşınabilirliği kaydedin (böylece bir çok montaj eski kodunu miras alır). Bu ASCII günlerinde null karakterlerin işe yaramayacağını kabul edersiniz, (ve muhtemelen bir EOF kontrol karakterleri kadar iyi).

görelim pseudo code

function readString(string) // 1 parameter: 1 register or 1 stact entries
    pointer=addressOf(string) 
    while(string[pointer]!=CONTROL_CHAR) do
        read(string[pointer])
        increment pointer

toplam 1 kayıt kullanımı

vaka 2

 function readString(length,string) // 2 parameters: 2 register used or 2 stack entries
     pointer=addressOf(string) 
     while(length>0) do 
         read(string[pointer])
         increment pointer
         decrement length

toplam 2 kayıt kullanıldı

Bu o zaman dar görüşlü görünebilir, ancak kod ve kayıttaki tutumluluğu göz önünde bulundurarak (o zaman PREMIUM idi, bildiğiniz zaman, delikli kart kullanıyorlar). Böylece daha hızlı olmak (işlemci hızı kHz olarak hesaplanabiliyorsa), bu "Hack" çok daha iyi ve taşınabilirdi.

Tartışma uğruna 2 ortak dize işlemi uygulayacağım

stringLength(string)
     pointer=addressOf(string)
     while(string[pointer]!=CONTROL_CHAR) do
         increment pointer
     return pointer-addressOf(string)

karmaşıklık O (n), burada çoğu durumda PASCAL dizesi O (1) 'dir, çünkü ipin uzunluğu ip yapısına eklenmiştir (bu aynı zamanda bu işlemin daha erken bir aşamada gerçekleştirilmesi gerektiği anlamına gelir).

concatString(string1,string2)
     length1=stringLength(string1)
     length2=stringLength(string2)
     string3=allocate(string1+string2)
     pointer1=addressOf(string1)
     pointer3=addressOf(string3)
     while(string1[pointer1]!=CONTROL_CHAR) do
         string3[pointer3]=string1[pointer1]
         increment pointer3
         increment pointer1
     pointer2=addressOf(string2)
     while(string2[pointer2]!=CONTROL_CHAR) do
         string3[pointer3]=string2[pointer2]
         increment pointer3
         increment pointer1
     return string3

karmaşıklığı O (n) ve dize uzunluğunu eklemek işlemin karmaşıklığını değiştirmezken, 3 kat daha az zaman alacağını itiraf ediyorum.

Diğer bir yandan, PASCAL dizesini kullanırsanız, kayıt uzunluğunu ve bit-endianitesini hesaba katmak için API'nizi yeniden tasarlamanız gerekir; PASCAL dizesi, uzunluk 1 bayt (8 bit) içinde saklandığı için 255 char (0xFF) iyi bilinen sınırlamasını aldı. ) ve daha uzun bir dize (16bits-> herhangi bir şey) istediğiniz kodun bir katmanındaki mimariyi hesaba katmanız gerekir, bu da daha uzun dize istiyorsanız çoğu durumda uyumsuz dize API'leri anlamına gelir.

Misal:

Bir dosya, 8 bitlik bir bilgisayarda önceden dize api ile yazılmış ve daha sonra 32 bitlik bir bilgisayar söylemek okunması gerekir, tembel program ne 4bayt dize uzunluğu olduğunu düşünüyor ne o kadar bellek ayırmak o kadar bayt okumaya çalışın. Başka bir durum, bir x86 (büyük endian) üzerine okunan PPC 32 bayt dizesi (küçük endian) olacaktır, elbette birinin diğeri tarafından yazıldığını bilmiyorsanız sorun olacaktır. 1 bayt uzunluğu (0x00000001), 1 bayt dizesini okumak için 16 MB olan 16777216 (0x0100000) olur. Tabii ki insanların tek bir standart üzerinde anlaşması gerektiğini söyleyebilirsin, ama 16 bitlik unicode bile çok az ve büyük bir endianiteye sahipti.

Tabii ki C'nin de sorunları olurdu ama burada ortaya çıkan sorunlardan çok az etkilenecekti.


2
@ deemoowoor: Concat: O(m+n)nullterm dizeleriyle, O(n)her yerde tipik. Diğer her yerde O(n)nullterm dizeleriyle uzunluk O(1). Katılın: O(n^2)nullterm dizeleriyle, diğer O(n)her yerde. Boş sonlandırılmış dizelerin daha verimli olduğu bazı durumlar vardır (yani yalnızca işaretçi durumuna bir tane ekleyin), ancak concat ve uzunluk en yaygın işlemlerdir (biçimlendirme, dosya çıktısı, konsol ekranı vb. İçin en azından uzunluk gereklidir). . Eğer itfa süresinin uzunluğunu önbelleğe O(n)alırsanız, sadece uzunluğun dizeyle birlikte saklanması gerektiğini söyledim.
Billy ONeal

1
Bugünün kodunda bu dize türünün verimsiz ve hataya eğilimli olduğunu kabul ediyorum, ancak örneğin Konsol ekranı gerçekten verimli bir şekilde görüntülemek için dizenin uzunluğunu bilmek zorunda değil, dosya çıktısının gerçekten dize hakkında bilmesine gerek yoktu uzunluk (sadece hareket halindeyken küme ayırma), Ve şu anda dize biçimlendirme çoğu durumda sabit bir dize uzunluğu üzerinde yapıldı. Her neyse, eğer C'deki bir O (n ^ 2) karmaşıklığı varsa, kötü kod yazmanız gerekir, O (n) karmaşıklığında bir tane yazabileceğimden eminim
dvhh 13:00

1
@dvhh: n ^ 2 demedim - m + n dedim - hala doğrusal, ancak birleştirme yapmak için orijinal dizenin sonuna bakmanız gerekiyor, oysa uzunluk öneki yok gerekli. (Bu, çizgisel zaman gerektiren uzunluğun başka bir sonucudur)
Billy ONeal

1
@Billy ONeal: sadece meraktan dolayı, mevcut C projemde (yaklaşık 50000 kod satırı) dize düzenleme işlevi çağrıları için bir grep yaptım. strlen 101, strcpy ve varyantları (strncpy, strlcpy): 85 (Ayrıca ileti, zımni kopyalar için kullanılan yüzlerce değişmez dizeye sahibim), strcmp: 56, strcat: 13 (ve 6, strncat'i çağırmak için sıfır uzunluklu dizeye birleştirmelerdir) . Ön ekli bir uzunluğu strlen çağrı hızlandıracak, ancak strcpy veya strcmp (belki strcmp API ortak önek kullanmıyorsa) kabul ediyorum. Yukarıdaki yorumlarla ilgili en ilginç şey, strcat'in çok nadir olmasıdır.
kriss

1
@supercat: pek değil, bazı uygulamalara bakın. Kısa dizeler kısa yığın tabanlı bir arabellek (yığın ayırma yok) kullanır, yalnızca yığın büyüdükçe kullanırlar. Ancak, bir kütüphane olarak fikrinizin gerçek bir uygulamasını sağlamaktan çekinmeyin. Genellikle sorunlar genel tasarımda değil, sadece detaylara ulaştığımızda ortaya çıkar.
kriss

9

Birçok yönden, C ilkeldi. Ve çok sevdim.

Montaj dilinin üzerinde bir adımdı ve size yazması ve bakımı çok daha kolay bir dille neredeyse aynı performansı veriyor.

Boş sonlandırıcı basittir ve dil tarafından özel bir destek gerektirmez.

Geriye dönüp baktığımda, o kadar uygun görünmüyor. Ama 80'lerde montaj dilini kullandım ve o zaman çok uygun görünüyordu. Sadece yazılımın sürekli geliştiğini ve platformların ve araçların sürekli daha karmaşıklaştığını düşünüyorum.


Boş sonlandırılmış dizeler hakkında artık ilkel olan şeyleri görmüyorum. Pascal C'den önce gelir ve uzunluk ön eki kullanır. Elbette, dize başına 256 karakterle sınırlıydı, ancak sadece 16 bit alan kullanmak, vakaların büyük çoğunluğunda sorunu çözerdi.
Billy ONeal

Karakter sayısını sınırlaması, tam da böyle bir şey yaparken düşünmeniz gereken sorunların türüdür. Evet, daha uzun sürebilirsin, ama o zamanlar baytlar önemliydi. Ve 16 bitlik bir alan tüm durumlar için yeterince uzun olacak mı? Hadi boş bir sonlandırmanın kavramsal olarak ilkel olduğunu kabul etmelisiniz.
Jonathan Wood

10
Dizenin uzunluğunu veya içeriği sınırlandırırsınız (boş karakter olmaz) ya da 4 ila 8 baytlık sayının fazladan ek yükünü kabul edersiniz. Ücretsiz öğle yemeği yok. Başlangıç ​​sırasında boş sonlandırılmış dize mükemmel bir anlam ifade etti. Montajda bazen bir dizenin sonunu işaretlemek için bir karakterin üst bitini kullandım ve bir bayt daha tasarruf ettim!
Mark Ransom

Aynen Mark: Ücretsiz öğle yemeği yok. Her zaman bir uzlaşmadır. Bugünlerde aynı türden tavizler vermeye gerek yok. Fakat o zamanlar bu yaklaşım diğer her şey kadar iyi görünüyordu.
Jonathan Wood

8

C'nin uygulandığı bir an için, uzunluklarına göre önek ekleyerek Pascal yolunu dizeleri varsayarsak: 7 karakter uzunluğunda bir dize, 3 karakterlik bir dize ile aynı VERİ TİPİ midir? Cevabınız evet ise, derleyiciyi ikincisine atadığımda derleyici ne tür bir kod üretmelidir? Dize kesilmeli veya otomatik olarak yeniden boyutlandırılmalı mı? Yeniden boyutlandırılırsa, bu işlem ipliğin güvenli olmasını sağlayacak bir kilitle mi korunmalıdır? C yaklaşımı tarafı tüm bu meselelere adım attı, ya da değil :)


2
Hata .. hayır olmadı. C yaklaşımı, 7 karakterlik dizenin 3 karakterlik dizeye atanmasına izin vermez.
Billy ONeal

@Billy ONeal: neden olmasın? Bu durumda anladığım kadarıyla, tüm dizeler aynı veri türündedir (char *), bu yüzden uzunluk önemli değildir. Pascal'ın aksine. Ancak bu, uzunluk ön ekli dizelerle ilgili bir sorundan ziyade Pascal'ın bir sınırlamasıydı.
Oliver Mason

4
@Billy: Sanırım Cristian'in fikrini yeni düzelttin. C bu konularla hiç ilgilenmeyerek ilgilenir. Hâlâ aslında C string kavramını içeren C cinsinden düşünüyorsunuz. Bu sadece bir işaretçi, böylece istediğiniz her şeye atayabilirsiniz.
Robert S Ciaccio

2
Bu matris gibi: "dize yok".
Robert S Ciaccio

1
@calavera: Bunun nasıl bir şey kanıtladığını anlamıyorum. Uzunluk ön eki ile aynı şekilde çözebilirsiniz ... yani atamaya hiç izin vermeyin.
Billy ONeal

8

Her nasılsa, C'de uzunluk ön ekli dizeler için derleyici desteği olmadığı anlamına gelen soruyu anladım. Aşağıdaki örnek, en azından dize uzunluklarının derleme zamanında sayıldığı kendi C dize kitaplığınızı başlatabileceğinizi gösterir:

#define PREFIX_STR(s) ((prefix_str_t){ sizeof(s)-1, (s) })

typedef struct { int n; char * p; } prefix_str_t;

int main() {
    prefix_str_t string1, string2;

    string1 = PREFIX_STR("Hello!");
    string2 = PREFIX_STR("Allows \0 chars (even if printf directly doesn't)");

    printf("%d %s\n", string1.n, string1.p); /* prints: "6 Hello!" */
    printf("%d %s\n", string2.n, string2.p); /* prints: "48 Allows " */

    return 0;
}

Bununla birlikte, bu dize işaretçisini ne zaman serbest bırakacağınıza ve statik olarak ne zaman tahsis edildiğine dikkat etmeniz gerektiğinden, hiçbir sorunla karşılaşmayacaktır (gerçek) char dizi) .

Düzenleme: Soruya daha doğrudan bir cevap olarak, benim görüşüm bu C hem de kullanılabilir bir dize uzunluğu (derleme zaman sabiti olarak) sahip destek olabilir yolu, ama yine de kullanmak istiyorsanız bellek ek yük ile sadece işaretçiler ve sıfır sonlandırma.

Tabii ki, sıfır uçlu dizelerle çalışmak önerilen uygulama gibiydi, çünkü standart kütüphane genel olarak dize uzunluklarını argüman olarak almaz ve uzunluğu ayıklamak char * s = "abc"örneğimin gösterdiği gibi basit bir kod değildir .


Sorun şu ki, kütüphaneler yapınızın varlığını bilmiyorlar ve yine de katıştırılmış boş değerler gibi şeyleri yanlış işliyorlar. Ayrıca, bu gerçekten sorduğum soruya cevap vermiyor.
Billy ONeal

1
Bu doğru. Yani daha büyük sorun, düz eski sıfır sonlu dizelerden ziyade dize parametreleriyle arabirimler sağlamanın daha iyi bir standart yolu olmamasıdır. Hala iddia ediyorum, işaretçi uzunluğu çiftleri (iyi, en azından onlarla bir C ++ std :: string inşa edebilirsiniz) besleme destekleyen kütüphaneler vardır.
Pyry Jahkola

2
Bir uzunluk saklasanız bile, katıştırılmış boş değerli dizelere asla izin vermemelisiniz. Bu temel sağduyu. Verilerinizde boş değerler varsa, bunları asla dizeler bekleyen işlevlerle kullanmamalısınız.
R .. GitHub DURDURMAK BUZA YARDIMCI OLMAK

1
@supercat: Güvenlik açısından bu fazlalığı memnuniyetle karşılarım. Aksi halde cahil (veya uykusuz) programcılar ikili veri ve dizeleri bitiştirmek ve bekliyoruz şeyler iletmeden sonunda [boş sonlandırılmış] dizeleri ...
R .. GitHub DUR YARDIMCI ICE

1
@R ..: Boş sonlandırılmış dizeler bekleyen char*yöntemler genellikle a beklerken, boş sonlandırma beklemeyen birçok yöntem de a bekler char*. Türleri ayırmanın daha önemli bir yararı Unicode davranışı ile ilgilidir. Bir dize uygulamasının, dizelerin belirli türde karakterler içerdiği bilinip bilinmediği veya bunları içermediği biliniyorsa bayrakları korumaya değer olabilir [örneğin, içermediği bilinen bir milyon karakterlik dizede 999,990 kod noktasını bulma temel çok dilli düzlemin ötesinde herhangi bir karakter daha hızlı büyüklük sırası olacak ...
supercat

6

"32 bitlik bir makinede bile, dizenin kullanılabilir belleğin boyutu olmasına izin verirseniz, uzunluğunda önek dizesi boş bir sonlandırılmış dizgiden yalnızca üç bayt daha geniş olur."

İlk olarak, kısa dizeler için fazladan 3 bayt ek yük olabilir. Özellikle, sıfır uzunluklu bir dize şimdi 4 kat daha fazla bellek almaktadır. Bazılarımız 64 bit makineler kullanıyoruz, bu yüzden sıfır uzunluklu bir dize saklamak için 8 bayta ihtiyacımız var veya dize biçimi platformun desteklediği en uzun dizelerle baş edemiyor.

Ele alınması gereken uyum sorunları da olabilir. "Solo \ 0second \ 0 \ 0four \ 0five \ 0 \ 0seventh" gibi 7 dize içeren bir bellek bloğum olduğunu varsayalım. İkinci dize ofset 5'te başlar. Donanım, 32-bit tam sayıların 4'ün katları olan bir adrese hizalanmasını gerektirebilir, bu nedenle dolgu ekleyerek ek yükü daha da artırabilirsiniz. C gösterimi, kıyaslamada çok bellek tasarrufludur. (Bellek verimliliği iyidir; örneğin önbellek performansına yardımcı olur.)


Soruda tüm bunları ele aldığımı düşünüyorum. Evet, x64 platformlarında 32 bit önek olası tüm dizelere sığamaz. Öte yandan, boş bir sonlandırılmış dize kadar büyük bir dize asla istemezsiniz, çünkü herhangi bir şey yapmak için, yapmak istediğiniz her işlemin sonunu bulmak için 4 milyar baytın tamamını incelemeniz gerekir. Ayrıca, boş sonlandırılmış dizelerin her zaman kötü olduğunu söylemiyorum - bu blok yapılardan birini oluşturuyorsanız ve özel uygulamanız bu tür bir yapı tarafından hızlanıyorsa, bunun için gidin. Keşke dilin varsayılan davranışı bunu yapmasaydı.
Billy ONeal

2
Sorunuzun bu kısmını alıntıladım çünkü bana göre verimlilik sorununun altında kaldı. Bellek gereksinimlerini iki katına çıkarmak veya dört katına çıkarmak (sırasıyla 16 bit ve 32 bit) büyük bir performans maliyeti olabilir. Uzun dizeler yavaş olabilir, ancak en azından desteklenirler ve hala çalışırlar. Diğer noktam, hizalama hakkında, hiç bahsetmiyorsun.
Brangdon

Hizalama, UCHAR_MAX'ın ötesindeki değerlerin bayt erişimleri ve bit kaydırma kullanılarak paketlenmiş ve paketlenmemiş gibi davranması gerektiğini belirterek çözülebilir. Uygun şekilde tasarlanmış bir dize türü, sıfır sonlu dizelerle karşılaştırılabilir depolama verimliliği sunarken, ek bellek yükü için tamponlar üzerinde sınır kontrolüne izin verir (bir tamponun "dolu" olup olmadığını söylemek için önekte bir bit kullanın; değil ve son bayt sıfır değil, bu bayt kalan alanı temsil eder .. Arabellek dolu değilse ve son bayt sıfırsa, son 256 bayt kullanılmaz, bu yüzden ...
supercat

... bu alanda sıfır ek bellek maliyeti ile kullanılmayan baytların tam sayısını kaydedebilir). Öneklerle çalışmanın maliyeti, dize uzunluğunu geçmek zorunda kalmadan fgets () gibi yöntemleri kullanma yeteneği ile dengelenecektir (çünkü tamponlar ne kadar büyük olduklarını bileceklerdir).
supercat

4

Boş sonlandırma, hızlı işaretçi tabanlı işlemlere izin verir.


5
Ha? Hangi "hızlı işaretçi işlemleri" uzunluk ön eki ile çalışmaz? Daha da önemlisi, uzunluk ön eki kullanan diğer diller, C wrt dizesi manipülasyonundan daha hızlıdır.
Billy ONeal

12
@billy: Uzunluk ön ekli dizelerle, yalnızca bir dize işaretçisi alıp ona 4 ekleyemezsiniz ve bunun geçerli bir dize olmasını beklemezsiniz, çünkü bir uzunluk önekine sahip değildir (zaten geçerli değil).
Jörgen Sigvardsson

3
@j_random_hacker: Birleştirme, olası O (n) yerine asciiz dizeleri (O (m + n)) için çok daha kötüdür ve concat, burada listelenen diğer işlemlerden çok daha yaygındır.
Billy ONeal

3
boş sonlandırılmış dizeleri ile daha pahalı hale biri tiiny küçük operasyon var: strlen. Bunun bir dezavantaj olduğunu söyleyebilirim.
jalf

10
@Billy ONeal: Diğer herkes normal ifadeyi de destekler. Ne olmuş yani ? Bunun için yaratılmış kütüphaneleri kullanın. C, piller dahil değil, maksimum verimlilik ve minimalizm ile ilgilidir. C araçları ayrıca, yapıları kullanarak Uzunluk Ön Ekli dizeyi çok kolay bir şekilde uygulamanızı sağlar. Ve hiçbir şey, kendi uzunluk ve karakter arabelleklerini yöneterek dize manipülasyon programlarını uygulamanızı yasaklamaz. Verimlilik istediğimde ve C'yi kullandığımda yaptığım şey genellikle budur, bir arabellek sonunda sıfır bekleyen bir avuç işlev çağırmamak sorun değildir.
kriss

4

Henüz belirtilmeyen bir nokta: C tasarlandığında, bir 'char' sekiz bit olmayan birçok makine vardı (bugün bile olmayan DSP platformları var). Dizelerin uzunluk ön ekine sahip olacağına karar verirse, kaç tane karakter uzunluğunda önek kullanılmalıdır? İkisini kullanmak, 8 bit karakterli ve 32 bit adresleme alanına sahip makineler için dize uzunluğu üzerinde yapay bir sınır getirirken, 16 bit karakterli ve 16 bit adresleme alanına sahip makinelerde yer harcar.

Eğer biri rasgele uzunluktaki dizelerin verimli bir şekilde saklanmasına izin vermek istiyorsa ve 'char' her zaman 8 bit olsaydı, biri hız ve kod boyutunda bir miktar masraf için - bir düzen tanımlayabilirdi. N, N / 2 bayt uzunluğundadır, tek bir N değeri ile ön eklenmiş bir dize ve çift M değeri (geriye doğru okuma) ((N-1) + M * char_max) / 2 vb. Olabilir ve bir dizeyi tutmak için belirli bir miktarda alan sunma iddiasında, o alandan önce maksimum uzunluğu işlemek için yeterli bayt bulunmalıdır. Bununla birlikte, 'char' ın her zaman 8 bit olmadığı gerçeği, böyle bir şemayı karmaşık hale getirecektir, çünkü bir dizenin uzunluğunu tutmak için gereken 'char' sayısı CPU mimarisine bağlı olarak değişecektir.


Önek, olduğu gibi, uygulama tarafından tanımlanan boyutta kolayca olabilir sizeof(char).
Billy ONeal

@BillyONeal: sizeof(char)biridir. Her zaman. Önek, uygulama tanımlı bir boyut olabilir, ancak garip olacaktır. Ayrıca, "doğru" boyutun ne olması gerektiğini bilmenin gerçek bir yolu yoktur. Biri çok sayıda 4 karakterli dizgeye sahipse, sıfır dolgu% 25 ek yük getirirken, dört bayt uzunluğunda bir önek% 100 ek yük getirecektir. Ayrıca, dört bayt uzunluktaki önekleri paketlemek ve paketinden çıkarmak için harcanan zaman, sıfır bayt için 4 bayt dizeleri tarama maliyetini aşabilir.
supercat

1
Ah evet. Haklısın. Önek kolayca char dışında bir şey olabilir. Hedef platformda hizalama gerekliliklerini ortaya çıkaracak her şey yoluna girecektir. Yine de oraya gitmeyeceğim - bunu zaten ölümüne tartıştım.
Billy ONeal

Dizelerin uzunluğa önceden eklenmiş olduğunu varsayarsak, muhtemelen yapılacak en güzel şey bir size_tönek olur (bellek israfı lanetlenir, en sağlıklı olurdu --- muhtemelen belleğe sığabilecek olası uzunluktaki dizelere izin verir). Aslında, işte tür Ge ne yaptığını; diziler struct { size_t length; T* ptr; }ve dizeler sadece dizilerdir immutable(char).
Tim Čas olarak

@ TimČas: Dizelerin kelime hizalaması gerekmedikçe, kısa dizelerle çalışma maliyetinin, birçok platformda uzunluğun paketlenmesi ve paketinden çıkarılması gerekliliği baskın olacaktır; Bunu gerçekten pratik olarak görmüyorum. Biri dizelerin içerik agnostik keyfi boyutta bayt dizileri olmasını istiyorsa, uzunluğu işaretçiden karakter verilerine ayırmanın daha iyi olacağını ve bir dizinin değişmez dizeler için elde edilmesine izin vermenin daha iyi olacağını düşünüyorum. .
supercat

2

C'yi çevreleyen birçok tasarım kararı, başlangıçta uygulandığında, parametre geçişinin biraz pahalı olmasından kaynaklanmaktadır. Örn.

void add_element_to_next(arr, offset)
  char[] arr;
  int offset;
{
  arr[offset] += arr[offset+1];
}

char array[40];

void test()
{
  for (i=0; i<39; i++)
    add_element_to_next(array, i);
}

karşı

void add_element_to_next(ptr)
  char *p;
{
  p[0]+=p[1];
}

char array[40];

void test()
{
  int i;
  for (i=0; i<39; i++)
    add_element_to_next(arr+i);
}

ikincisi, iki yerine sadece bir parametrenin geçirilmesini gerektirdiğinden, biraz daha ucuz olurdu (ve dolayısıyla tercih edilir). Eğer çağrılan yöntemin dizinin ya da içindeki dizinin temel adresini bilmesine gerek yoksa, ikisini birleştiren tek bir işaretçiyi geçmek değerleri ayrı ayrı iletmekten daha ucuz olacaktır.

C'nin dize uzunluklarını kodlayabilmesinin birçok makul yolu olsa da, o zamana kadar icat edilen yaklaşımlar, dizenin temel adresini kabul etmek için bir dizenin parçası ile çalışabilmesi gereken tüm gerekli işlevlere sahip olacaktı ve istenen indeksi iki ayrı parametre olarak gösterir. Sıfır baytlı sonlandırma kullanılması, bu gereksinimin ortadan kaldırılmasını mümkün kıldı. Her ne kadar günümüzdeki makinelerle diğer yaklaşımlar daha iyi olsa da (modern derleyiciler genellikle kayıtları kayıt parametrelerine geçirir ve memcpy strcpy () - eşdeğerlerinin yapamayacağı şekilde) optimize edilebilir.

Not - Bazı işlemlerde hafif bir hız cezası ve daha uzun dizelerde biraz fazladan ek yük olması karşılığında, dizelerle çalışan yöntemlerin doğrudan dizelere, sınır denetimli dize arabelleklerine veya başka bir dizenin alt dizelerini tanımlayan veri yapıları. "Strcat" gibi bir işlev [modern sözdizimi]

void strcat(unsigned char *dest, unsigned char *src)
{
  struct STRING_INFO d,s;
  str_size_t copy_length;

  get_string_info(&d, dest);
  get_string_info(&s, src);
  if (d.si_buff_size > d.si_length) // Destination is resizable buffer
  {
    copy_length = d.si_buff_size - d.si_length;
    if (s.src_length < copy_length)
      copy_length = s.src_length;
    memcpy(d.buff + d.si_length, s.buff, copy_length);
    d.si_length += copy_length;
    update_string_length(&d);
  }
}

K&R strcat yönteminden biraz daha büyük, ancak K&R yönteminin desteklemediği sınır kontrolünü destekleyecekti. Ayrıca, mevcut yöntemin aksine, rasgele bir alt dizeyi, örneğin ör.

/* Concatenate 10th through 24th characters from src to dest */

void catpart(unsigned char *dest, unsigned char *src)
{
  struct SUBSTRING_INFO *inf;
  src = temp_substring(&inf, src, 10, 24);
  strcat(dest, src);
}

Temp_substring tarafından döndürülen dize ömrü olanlar ile sınırlı olacağı Not sve srcyöntem gerektirir neden şimdiye olan (daha kısa olduğu,inf aktarılması - yerel olsaydı, yöntem döndürüldüğünde ölür).

Bellek maliyeti açısından, 64 bayta kadar olan dizeler ve arabelleklerin bir bayt ek yükü olacaktır (sıfır sonlu dizelerle aynı); daha uzun dizeler biraz daha fazla olabilir (bir kişinin iki bayt arasında izin verilen miktarlarda ek yüke izin verip vermediği, bir zaman / boşluk ödünleşimi olurdu). Uzunluk / mod baytının özel bir değeri, bir dize işlevine bir bayrak baytı, bir işaretçi ve bir arabellek uzunluğu içeren bir yapı verildiğini (daha sonra isteğe bağlı olarak başka bir dizeye dizine ekleyebileceğini) belirtmek için kullanılır.

Tabii ki, K&R böyle bir şey uygulamadı, ancak bunun nedeni büyük olasılıkla ipin işlenmesi için çok fazla çaba harcamak istemiyordu - bugün bile birçok dilin anemik göründüğü bir alan.


char* arrFormun struct { int length; char characters[ANYSIZE_ARRAY] };veya benzerinin tek bir parametre olarak geçirilebilecek bir yapısına işaret etmesini engelleyecek hiçbir şey yoktur .
Billy ONeal

@BillyONeal: Bu yaklaşımla ilgili iki sorun: (1) Sadece ipin bir bütün olarak geçmesine izin verirken, mevcut yaklaşım aynı zamanda bir ipin kuyruğunun geçmesine izin verir; (2) küçük tellerle kullanıldığında önemli ölçüde boşa harcanacaktır. Eğer K&R dizelerde biraz zaman geçirmek isteseydi, işleri daha sağlam hale getirebilirlerdi, ama yeni dillerinin on yıl sonra, daha az kırk yaşlarında kullanılmasını amaçladıklarını düşünmüyorum.
supercat

1
Çağıran konvansiyonla ilgili bu bit, gerçeklikle hiçbir ilgisi olmayan adil bir hikaye ... tasarımda bir düşünce değildi. Ve kayıt temelli arama kuralları zaten "icat edilmişti". Ayrıca, iki işaretçi gibi yaklaşımlar bir seçenek değildi çünkü yapılar birinci sınıf değildi ... sadece ilkel unsurlar atanabilir veya geçilebilirdi; yapısal kopyalama UNIX V7'ye kadar gelmedi. Bir dize işaretçisi kopyalamak için memcpy (aynı zamanda mevcut değildi) ihtiyacı bir şaka. Bir dil tasarımı iddiası yapıyorsanız, sadece yalıtılmış fonksiyonlar değil, tam bir program yazmayı deneyin.
Jim Balter

1
“bu büyük olasılıkla ipin işlenmesi için fazla çaba harcamak istemedikleri için” - saçmalık; UNIX'in erken uygulama alanının tamamı dize işleme idi. Eğer böyle olmasaydı, bunu hiç duymazdık.
Jim Balter

1
'Sanmıyorum' char arabellek uzunluğu içeren bir int ile başlar "daha büyülü '- eğer str[n]doğru char referans yapacaksanız . Bunlar, bunu tartışan insanların düşünmediği şeylerdir .
Jim Balter

2

Bu blog yazısında Joel Spolsky'ye göre ,

Bunun nedeni, UNIX ve C programlama dilinin icat edildiği PDP-7 mikroişlemcinin ASCIZ dize tipine sahip olmasıdır. ASCIZ "sonunda Z (sıfır) olan ASCII" anlamına geliyordu.

Buradaki diğer tüm cevapları gördükten sonra, bu doğru olsa bile, C'nin boş sonlandırılmış "dizelere" sahip olmasının nedeninin sadece bir parçası olduğuna ikna oldum. Bu yazı, dizeler gibi basit şeylerin aslında oldukça zor olabileceği konusunda oldukça aydınlatıcı.


2
Bak, Joel'e birçok şey için saygı duyuyorum; ama bu onun spekülasyon yaptığı bir şey. Hans Passant'ın cevabı doğrudan C'nin mucitlerinden geliyor.
Billy ONeal

1
Evet, ama Spolsky'nin söylediği şey doğruysa, söz ettikleri "rahatlığın" bir parçası olurdu. Bu yüzden kısmen bu cevabı ekledim.
BenK

AFAIK .ASCIZsadece bir bayt dizisi oluşturmak için bir montajcı deyimiydi ve bunu izledi 0. Bu sadece sıfır sonlandırılmış dizginin o zaman iyi kurulmuş bir kavram olduğu anlamına gelir . O mu değil sıfır sonlandırılmış dizeleri oluşan sıkı döngüler yazabilirsiniz dışında bir PDP- * mimarisi ile ilgili birşey olduğu anlamına MOVBve (bir byte kopyalamak) BNE(kopyalanan son bayt değil sıfır olsaydı dalı).
Adrian W

C'nin eski, sarkık, yıpranmış bir dil olduğunu göstermesi gerekir.
purec

2

Gerekçeli olmak zorunda değil, uzunluk kodlamalı bir karşı konu

  1. Bazı dinamik uzunluk kodlaması biçimleri, bellek açısından statik uzunluk kodlamasından daha üstündür, hepsi kullanıma bağlıdır. Kanıt için UTF-8'e bakmanız yeterli. Temelde tek bir karakteri kodlamak için genişletilebilir bir karakter dizisidir. Bu, her genişletilmiş bayt için tek bir bit kullanır. NUL sonlandırmasında 8 bit kullanılır. Uzunluk öneki bence 64 bit kullanılarak da makul uzunlukta sonsuz uzunluk olarak adlandırılabilir. Ekstra bitlerinizin durumuna ne sıklıkta çarptığınız belirleyici faktördür. Sadece 1 aşırı büyük dize? 8 veya 64 bit kullanıyorsanız kimin umrunda? Birçok küçük dize (İngilizce kelimelerin Ie Dizeleri)? O zaman ön ek maliyetleriniz büyük bir yüzdedir.

  2. Zamandan tasarruf sağlayan uzunluk ön ekli dizeler gerçek bir şey değildir . Sağlanan verilerinizin uzunluğunun sağlanması gerekip gerekmediği, derleme zamanında sayılıyor veya dize olarak kodlamanız gereken dinamik veriler sağlanıyor. Bu boyutlar algoritmanın bir noktasında hesaplanır. Ayrı bir değişken boş sonlandırılmış dizesi boyutunu depolamak için sağlanır. Bu da zaman tasarrufu tartışmasını yapar. Birinin sonunda fazladan bir NUL var ... ama eğer uzunluk kodlaması bu NUL'u içermiyorsa, ikisi arasında hiçbir fark yoktur. Hiçbir algoritmik değişiklik gerekmez. Sadece bir ön geçiş bir derleyici / çalışma zamanı sizin için yapmak yerine kendinizi kendiniz tasarlamanız gerekir. C çoğunlukla işleri elle yapmakla ilgilidir.

  3. Uzunluk öneki isteğe bağlı olmak bir satış noktasıdır. Ben her zaman bir algoritma için ekstra bilgi gerekmez her dize için yapmak için gerekli olmak benim precompute + işlem süresi asla O (n) altına düşmek mümkün kılar. (Yani donanım rasgele sayı üreteci 1-128. Bir "sonsuz dize" alabilirim. Diyelim ki sadece çok hızlı karakterler üretir. Dize uzunluğu her zaman değişir. Ama veri kullanımı benim muhtemelen nasıl umurumda değil birçok rastgele bayt var.Sadece bir istek üzerine alır almaz bir sonraki kullanılabilir kullanılmayan bayt istiyor.Cihazda bekliyor olabilirim.Ama aynı zamanda önceden okunmuş bir karakter tamponu da olabilir. gereksiz bir hesaplama israfıdır. Boş kontrol daha verimlidir.)

  4. Uzunluk öneki, arabellek taşmasına karşı iyi bir koruma mı? Kitaplık fonksiyonlarının akılcı kullanımı ve uygulanması da böyledir. Yanlış biçimlendirilmiş verilerden geçersem ne olur? Arabelleğim 2 bayt uzunluğunda ama fonksiyona 7 olduğunu söylüyorum! Örn: gets () derlenmiş tamponlar ve test edilen bir iç tampon kontrolü oldu olabilir bilinen veriler üzerinde kullanılmak üzere amaçlanmıştır ) (Malloc çağırıyorsa ve hala spec'i takip ediyorsanız . Bilinmeyen STDIN'in bilinmeyen tampona ulaşması için bir boru olarak kullanılması gerekiyorsa, açıkça bir tamponun boyutuna dayanamaz, bu da uzunluk argusunun anlamsız olduğu anlamına gelir, burada bir kanarya kontrolü gibi başka bir şeye ihtiyacınız vardır. Bu nedenle, bazı akışların ve girişlerin uzunluğunda önek ekleyemezsiniz, sadece yapamazsınız. Bu, uzunluk kontrolünün, yazma sisteminin sihirli bir parçası değil, algoritmaya dahil edilmesi gerektiği anlamına gelir. TL; DR NUL tarafından sonlandırılan hiçbir zaman güvensiz olmak zorunda değildi, sadece yanlış kullanımla bu şekilde sona erdi.

  5. karşı-karşı noktası: NUL-sonlandırma ikili üzerinde can sıkıcıdır. Burada uzunluk öneki yapmanız veya bir şekilde NUL baytlarını dönüştürmeniz gerekir: kaçış kodları, aralık yeniden eşleme, vb ... bu da elbette daha fazla bellek kullanımı / azaltılmış bilgi / bayt başına daha fazla işlem anlamına gelir. Uzunluk öneki çoğunlukla savaşı kazanır. Bir dönüşümün tek tarafı, uzunluk öneki dizelerini kapsayacak ek işlevlerin yazılmasına gerek olmamasıdır. Bu, daha optimize alt O (n) yordamlarınızda, daha fazla kod eklemeden otomatik olarak O (n) eşdeğerleri olarak hareket etmelerini sağlayabilirsiniz. Olumsuz, elbette, NUL ağır tellerde kullanıldığında zaman / bellek / sıkıştırma kaybıdır.Kütüphanenizin ne kadarının ikili veriler üzerinde çalışmak üzere çoğaltıldığına bağlı olarak, yalnızca uzunluk öneki dizeleriyle çalışmak mantıklı olabilir. Birinin uzunluk önek dizeleriyle de aynısını yapabileceği söylenir ... -1 uzunluk, NUL sonlandırması anlamına gelebilir ve uzunluk sonlandırılmış içinde NUL sonlandırılmış dizeleri kullanabilirsiniz.

  6. Concat: "O (n + m) vs O (m)" Birleştirmeden sonra dizeye toplam uzunluk olarak m'ye atıfta bulunduğunuzu varsayıyorum çünkü her ikisinin de minimum işlem sayısına sahip olması gerekir (sadece -dize 1'e, yeniden tahsis etmeniz gerekiyorsa?). Ve n'nin bir ön hesaplama nedeniyle artık yapmak zorunda olmadığınız efsanevi bir işlem olduğunu varsayıyorum. Öyleyse, cevap basittir: ön hesaplama. Eğerher zaman yeniden tahsis etmek için yeterli belleğe sahip olacağınız konusunda ısrar edersiniz ve bu büyük O gösteriminin temelidir, o zaman cevap daha da basittir: dize 1'in sonu için ayrılan bellekte ikili arama yapın, açıkça büyük bir şey var realloc hakkında endişelenmemek için dize 1'den sonra sonsuz sıfırların renk örneği. Orada, kolayca n giriş (n) var ve zorlukla denedim. Eğer günlük (n) 'yi hatırlarsanız gerçek bir bilgisayarda sadece 64 kadar büyüktür, bu da esasen O (m) olan O (64 + m) demek gibidir. (Ve evet bu mantık bugün kullanımda olan gerçek veri yapılarının çalışma zamanı analizinde kullanılmıştır. Başımın üstünden saçmalık değil.)

  7. Concat () / Len () tekrar : Sonuçları not edin. Kolay. Mümkünse / gerekirse tüm hesaplamaları ön hesaplamalara dönüştürür. Bu algoritmik bir karardır. Bu, dilin zorunlu bir kısıtlaması değildir.

  8. Dize sonlandırma ile dize soneki iletimi daha kolay / mümkündür. Uzunluk önekinin nasıl uygulandığına bağlı olarak, orijinal dizede yıkıcı olabilir ve bazen bile mümkün olmayabilir. Kopyalama ve O (1) yerine O (n) geçmesi gerekiyor.

  9. Bağımsız değişken geçirme / referans gösterme, uzunluk önekine göre NUL sonlandırması için daha azdır. Açıkçası daha az bilgi aktardığınız için. Uzunluğa ihtiyacınız yoksa, bu çok fazla yer tasarrufu sağlar ve optimizasyonlara izin verir.

  10. Hile yapabilirsiniz. Gerçekten sadece bir işaretçi. Kim bir dize olarak okumak zorunda diyor? Tek bir karakter veya kayan nokta olarak okumak isterseniz ne olur? Ya tam tersini yapmak ve bir kayan reklamı dize olarak okumak isterseniz? Eğer dikkatli olursanız, bunu NUL sonlandırma ile yapabilirsiniz. Uzunluk önekiyle bunu yapamazsınız, bu genellikle bir işaretçiden belirgin şekilde farklı bir veri türüdür. Büyük olasılıkla bir bayt bayt dizesi oluşturmanız ve uzunluğu elde etmeniz gerekir. Tabii ki tüm bir şamandıra gibi bir şey istiyorsanız (muhtemelen içinde bir NUL vardır) yine de bayt bayt okumak zorunda kalırsınız, ancak ayrıntılar karar vermek için size bırakılır.

TL; DR İkili veri mi kullanıyorsunuz? Hayır ise, NUL sonlandırma daha fazla algoritmik özgürlüğe izin verir. Evet ise, hız / bellek / sıkıştırmaya karşı kod miktarı ana endişenizdir. İki yaklaşımın veya notun bir karışımı en iyisi olabilir.


9 biraz temel dışı / yanlış temsil edildi. Uzunluk ön düzeltmesinde bu sorun yoktur. Lenth ayrı bir değişken olarak geçer . Pre-fiix hakkında konuşuyorduk ama taşındım. Düşünmek için hala iyi bir şey, o yüzden orada bırakacağım. : d
Siyah

1

"C dizesi yok" cevabını satın almıyorum. Doğru, C yerleşik üst düzey türleri desteklemez, ancak yine de C'deki veri yapılarını temsil edebilirsiniz ve bir dize budur. Bir dizginin sadece C'deki bir işaretçi olması, ilk N baytın uzunluk olarak özel bir anlam taşıyamayacağı anlamına gelmez.

Windows / COM geliştiricileri BSTR, tam olarak böyle olan türe çok aşinadır - gerçek karakter verilerinin bayt 0'da başlamadığı, uzunluk ön ekli bir C dizesi.

Öyleyse, null sonlandırma kullanma kararı, dilin bir zorunluluğu değil, insanların tercih ettiği şeydir.


-3

gcc aşağıdaki kodları kabul eder:

char s [4] = "abcd";

ve biz tedavi eğer karakter dizisi olarak ama dize değil tamam. Yani, s [0], s [1], s [2] ve s [3] ile veya hatta memcpy (dest, s, 4) ile erişebiliriz. Ancak, koyar (lar) ile denediğimizde ya da strcpy (dest, s) ile daha da kötüyken dağınık karakterler elde edeceğiz.


@Adrian W. Bu geçerlidir C. Tam uzunluktaki dizeler özel kasalıdır ve onlar için NUL atlanmıştır. Bu genellikle mantıksız bir uygulamadır, ancak FourCC "dizeleri" kullanan başlık yapılarını doldurmak gibi durumlarda yararlı olabilir.
Kevin Thibedeau

Haklısın. Bu geçerli C'dir, derlenir ve kkaaii açıklandığı gibi davranır. Aşağı oyların nedeni (benim değil ...) muhtemelen bu cevabın OP'nin sorusuna hiçbir şekilde cevap vermemesidir.
Adrian W
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.