Bir karakter [] dizgesel değişmez kötü uygulama ile başlatılıyor mu?


44

Ben başlıklı bir konu okuyordu CodeGuru üzerinde "sizeof vs strlen" ve cevaplar biri devletler "o zaten initialie [sic] a [sic] kötü uygulama olduğunu charbir dize ile dizide."

Bu doğru mu, yoksa sadece ("seçkin bir üye" de olsa) görüşü mü?


İşte asıl soru:

#include <stdio.h>
#include<string.h>
main()
{
    char string[] = "october";
    strcpy(string, "september");

    printf("the size of %s is %d and the length is %d\n\n", string, sizeof(string), strlen(string));
    return 0;
}

sağ. boy uzunluğu artı 1 evet olmalıdır?

bu çıktı

the size of september is 8 and the length is 9

boyutu kesinlikle 10 olmalıdır. onun strcpy ile değiştirilmeden önceki dizgenin boyutunu hesaplamak gibi bir şey.

Sözdizimde yanlış olan bir şey mi var?


İşte cevap :

Yine de, bir dizgenin değişmezi ile bir karakter dizisi başlatmak kötü bir uygulamadır. Dolayısıyla daima aşağıdakilerden birini yapın:

const char string1[] = "october";
char string2[20]; strcpy(string2, "september");

İlk satırdaki "const" ifadesine dikkat edin. Yazarın c yerine c ++ olduğu varsayılabilir mi? C ++ 'da "kötü uygulama" dır, çünkü değişmezin const olması gerekir ve yakın zamanda c ++ derleyicilerinin const olmayan bir diziye const değişmezi ataması hakkında bir uyarı (veya hata) verilir.
André

@ André C ++ string değişmezlerini const dizileri olarak tanımlar, çünkü onlarla başa çıkmanın tek güvenli yolu budur. Bu C sorun değil , bu yüzden güvenli olanı uygulayan sosyal bir kuralınız var
Caleth

@Caleth. Biliyorum, daha çok cevabın yazarının "kötü uygulamalar" a c ++ bakış açısıyla yaklaştığını iddia etmeye çalışıyordum.
André

@ André, C ++ 'da kötü bir uygulama değildir, çünkü bir uygulama değildir , dolaysız bir tip hatasıdır. Bu olmalı C tipi bir hata, ancak bu bildiren bir stil kılavuzu kuralı olması gerekir bu yüzden "için yasak", değil
Caleth

Yanıtlar:


59

Yine de, bir dizgenin değişmezi ile bir karakter dizisi başlatmak kötü bir uygulamadır.

Bu yorumun yazarı hiçbir zaman haklı çıkarmaz ve ifadeyi şaşırtıcı buluyorum.

C'de (ve bunu C olarak etiketlediniz), dizge değeri olan bir diziyi başlatmanın tek yolu bu char(başlatma atamadan farklıdır). Ya yazabilirsiniz

char string[] = "october";

veya

char string[8] = "october";

veya

char string[MAX_MONTH_LENGTH] = "october";

İlk durumda, dizinin boyutu, başlatıcının boyutundan alınır. Dize değişmezleri, char0 baytlık sonlandırıcı bir dizi halinde saklanır , bu nedenle dizinin boyutu 8'dir ('o', 'c', 't', 'o', 'b', 'e', ​​'r', 0). İkinci iki durumda, dizinin boyutu, bildirimin bir parçası olarak belirtilir (8 ve MAX_MONTH_LENGTH, ne olursa olsun).

Ne olamaz yaptığı gibi yazma şeydir

char string[];
string = "october";

veya

char string[8];
string = "october";

vb İlk durumda, beyanı stringolduğunu tamamlanmamış hiçbir dizi boyutu belirtilen edildiği için ve gelen boyutunu almak için hiçbir başlatıcı var. Her iki durumda =da işe yaramaz çünkü a) Bir dizinin ifadesi stringbir ödevin hedefi olmayabilir ve b) =Operatör bir dizinin içeriğini bir başkasına kopyalamak için tanımlanmadı.

Aynı şekilde yazı yazamazsın.

char string[] = foo;

foobaşka bir dizi nerede char. Bu başlatma şekli sadece string değişmezleri ile çalışacaktır.

DÜZENLE

Bunu, bir dizi-tarzı başlatıcıya sahip bir dize tutacak dizileri de başlatabileceğinizi söylemek için değiştirmeliyim.

char string[] = {'o', 'c', 't', 'o', 'b', 'e', 'r', 0};

veya

char string[] = {111, 99, 116, 111, 98, 101, 114, 0}; // assumes ASCII

ancak sicim harflerini kullanmak gözler için daha kolaydır.

EDIT 2

Bir dizinin içeriğini bir bildirimin dışında atamak için ya strcpy/strncpy(0-sonlandırılmış dizeler için) ya da memcpy(diğer herhangi bir diziler için) kullanmanız gerekir:

if (sizeof string > strlen("october"))
  strcpy(string, "october");

veya

strncpy(string, "october", sizeof string); // only copies as many characters as will
                                           // fit in the target buffer; 0 terminator
                                           // may not be copied, but the buffer is
                                           // uselessly completely zeroed if the
                                           // string is shorter!


@KeithThompson: katılmıyorum, sadece bütünlüğü uğruna ekledi.
John Bode

16
Lütfen bunun char[8] str = "october";kötü bir uygulama olduğunu unutmayın . Tam anlamıyla Char emin bir taşma değildi yapmak için kendimi saymak vardı ve bir imla hatasını düzeltmek ... mesela bakım altında kırar seprateiçin separateboyut güncellenen değilse kıracak.
djechlin

1
Djechlin ile aynı fikirdeyim, verilen sebeplerden dolayı bu kötü bir uygulama. JohnBode'un cevabı, "kötü uygulama" yönü (bu sorunun asıl kısmıdır !!) hakkında yorum yapmaz, sadece diziyi başlatmak için yapabileceklerinizi veya yapamadıklarınızı açıklar.
mastov

Minör: itibaren 'uzunluk" değeri döndürülür strlen()kullanarak, null karakteri içermez MAX_MONTH_LENGTHiçin gerekli maksimum boyut tutmak için char string[]sık sık görünüyor . Yanlış IMO, MAX_MONTH_SIZEburada daha iyi olurdu.
chux - Eski Monica

10

Hatırladığım tek sorun, tam anlamıyla string değişimini atamak char *:

char var1[] = "september";
var1[0] = 'S'; // Ok - 10 element char array allocated on stack
char const *var2 = "september";
var2[0] = 'S'; // Compile time error - pointer to constant string
char *var3 = "september";
var3[0] = 'S'; // Modifying some memory - which may result in modifying... something or crash

Örneğin bu programı alın:

#include <stdio.h>

int main() {
  char *var1 = "september";
  char *var2 = "september";
  var1[0] = 'S';
  printf("%s\n", var2);
}

Bu benim platformumda (Linux) salt okunur olarak işaretlenmiş sayfaya yazmaya çalıştığında çöküyor. Diğer platformlarda 'Eylül' yazdırabilir.

Söylendi - değişmez tarafından başlatma, belirli miktarda rezervasyon yapar, böylece bu işe yaramaz:

char buf[] = "May";
strncpy(buf, "September", sizeof(buf)); // Result "Sep"

Ama bu olacak

char buf[32] = "May";
strncpy(buf, "September", sizeof(buf));

Son olarak - Ben hiç kullanmazdım strcpy:

char buf[8];
strcpy(buf, "very long string very long string"); // Oops. We overwrite some random memory

Bazı derleyiciler güvenli aramaya dönüştürebilse strncpyde çok daha güvenlidir:

char buf[1024];
strncpy(buf, something_else, sizeof(buf)); // Copies at most sizeof(buf) chars so there is no possibility of buffer overrun. Please note that sizeof(buf) works for arrays but NOT pointers.
buf[sizeof(buf) - 1] = '\0';

Tampon aşması için hala bir risk var strncpyçünkü uzunluğu something_elsedaha büyük olduğunda kopyalanan dizgeyi boş bırakmaz sizeof(buf). Genelde bundan buf[sizeof(buf)-1] = 0korunacak son karakteri ayarladım veya bufsıfır başlatılmışsa sizeof(buf) - 1kopya uzunluğu olarak kullanın .
syockit

Kullanım strlcpyveya strcpy_shatta snprintfIçin varsa.
kullanıcı253751

Sabit. Maalesef, en yeni derleyicilerle çalışma lüksünüz olmadığı sürece ( strlcpyve snprintfMSVC'de doğrudan erişilemiyorsa, en azından emir ve strcpy_s* nix'te bulunmadığınız sürece), bunu yapmanın kolay bir yolu yoktur .
Maciej Piechotka

@ MaciejPiechotka: Tanrıya şükür, Unix microsoft sponsorluğundaki eki k reddetti.
Deduplicator

6

Her iki parçanın da getiremediği şeylerden biri şudur:

char whopping_great[8192] = "foo";

vs.

char whopping_great[8192];
memcpy(whopping_great, "foo", sizeof("foo"));

Eski gibi bir şey yapacak:

memcpy(whopping_great, "foo", sizeof("foo"));
memset(&whopping_great[sizeof("foo")], 0, sizeof(whopping_great)-sizeof("foo"));

İkincisi sadece memcpy yapar. C standardı, bir dizinin herhangi bir kısmının başlatılması durumunda hepsinin olduğu konusunda ısrar eder. Yani bu durumda, kendin yapmak daha iyidir. Sanırım Treuss'un aldığı şey bu olabilirdi.

Kesinlikle

char whopping_big[8192];
whopping_big[0] = 0;

ikisinden de iyidir:

char whopping_big[8192] = {0};

veya

char whopping_big[8192] = "";

ps Bonus puanları için şunları yapabilirsiniz:

memcpy(whopping_great, "foo", (1/(sizeof("foo") <= sizeof(whopping_great)))*sizeof("foo"));

Bir derleme zamanı atmak için diziden taşmak üzereyseniz sıfır hata ile bölün.


5

Öncelikle char[], program içinde kolayca kullanabileceğiniz bir değişken / yapı içinde boyutuna sahip olmayacağınız için.

Bağlantıdaki kod örneği:

 char string[] = "october";
 strcpy(string, "september");

stringyığında 7 veya 8 karakter uzunluğunda tahsis edilir. Bu şekilde null sonlandırılıp sonlandırılmadığını hatırlayamıyorum - bağladığınız iş parçacığı olduğunu belirtti.

"Eylül" kelimesini bu dizgenin üzerine kopyalamak, açık bir bellek aşımıdır.

Başka stringbir işleve geçerseniz , diğer işlevin diziye yazabilmesi için bir başka zorluk ortaya çıkar . Çok dizidir ne kadar diğer işlevi söylememe gerek o aşılmasına oluşturmaz. Sonucu stringile birlikte geçebilirsiniz , strlen()ancak konu stringboş bırakılmazsa bunun nasıl patlayabileceğini açıklar .

Sabit bir boyuta sahip bir dize (tercihen sabit olarak tanımlanır) atamaktan daha iyi bir durumdasınız ve daha sonra diziyi ve sabit boyutu diğer işleve geçirin. @John Bode adlı kullanıcının yorumları doğru ve bu riskleri azaltmanın yolları var. Ayrıca, bunları kullanmak için sizin tarafınızdan daha fazla çaba gösterilmesini gerektirir.

Deneyimlerime göre, başlattığım değer, char[]buraya yerleştirmem gereken diğer değerler için genellikle çok küçük. Tanımlanmış bir sabit kullanmak, bu sorunun önlenmesine yardımcı olur.


sizeof stringsize arabellek boyutunu verecektir (8 bayt); strlenHafızadan endişe duymak yerine bu ifadenin sonucunu kullanın .
Çağrısı önce Benzer şekilde, bir çek yapabilir strcpyhedef tampon kaynak dizesi için yeterince büyük olup olmadığını görmek için: if (sizeof target > strlen(src)) { strcpy (target, src); }.
Eğer bir işleve dizi geçmek zorunda Evet, eğer siz de fiziksel boyutunu geçmesi gerekir: foo (array, sizeof array / sizeof *array);. - John Bode


2
sizeof stringsize arabellek boyutunu verecektir (8 bayt); strlenHafızadan endişe duymak yerine bu ifadenin sonucunu kullanın . Çağrısı önce Benzer şekilde, bir çek yapabilir strcpyhedef tampon kaynak dizesi için yeterince büyük olup olmadığını görmek için: if (sizeof target > strlen(src)) { strcpy (target, src); }. Eğer bir işleve dizi geçmek zorunda Evet, eğer siz de fiziksel boyutunu geçmesi gerekir: foo (array, sizeof array / sizeof *array);.
John Bode

1
@JohnBode - teşekkürler ve bunlar iyi noktalar. Yorumunuzu cevabıma ekledim.

1
Daha doğrusu, dizi adına yapılan çoğu referans, dizinin ilk öğesini işaret eden stringörtük bir dönüşümle sonuçlanır char*. Bu dizi sınır bilgileri kaybeder. Bir işlev çağrısı, bunun gerçekleştiği birçok bağlamdan yalnızca biridir. char *ptr = string;başka bir şey. Buna bile string[0]bir örnek; []Operatör doğrudan değil diziler üzerinde, işaretçiler üzerinde çalışır. Önerilen okumalar: comp.lang.c SSS bölümünün 6. kısmı .
Keith Thompson

Sonunda aslında soruyu ifade eden bir cevap!
mastov

2

Bence "kötü uygulama" fikri bu formdan kaynaklanıyor:

char string[] = "october is a nice month";

dolaylı olarak kaynak makine kodundan yığına bir strcpy yapar.

Bu dizeye sadece bir link vermek daha verimlidir. Gibi:

char *string = "october is a nice month";

veya doğrudan:

strcpy(output, "october is a nice month");

(ama elbette çoğu kodda muhtemelen önemli değil)


Değiştirmeye çalışırsanız sadece bir kopya çıkarmaz mıydı? Derleyicinin bundan daha akıllı olacağını düşünüyorum
Cole Johnson

1
char time_buf[] = "00:00";Bir tamponu değiştireceğiniz yerler gibi durumlara ne dersiniz ? Bir char *dize değişmezine ilklendirilen değer, ilk baytın adresine ayarlanır, bu nedenle onu değiştirmeye çalışmak tanımsız davranışa neden olur çünkü dize değişmezinin depolamasının yöntemi bilinmemektedir (uygulama tanımlanmıştır), dize değişmezlerinin char[]tamamen yasal olması nedeniyle başlatma, baytları yığında ayrılan yazılabilir bir alana kopyalar. Bunun nüansları üzerinde durmadan "daha az verimli" veya "kötü uygulama" olduğunu söylemek char* vs char[]yanıltıcıdır.
Braden Best

-3

Asla çok uzun bir zaman değil, ancak karakter dizgesini char [] dizgisine çevirmekten kaçınmalısınız, çünkü "string" const char * olur ve siz onu char * a atarsınız. Dolayısıyla, eğer bu karakteri [] veriyi değiştiren yönteme geçirirseniz ilginç davranışlarda bulunabilirsiniz.

Commend dediğim gibi char * ile biraz char [] karıştırdım, biraz farklı olduklarından iyi değil.

Char dizisine veri atama konusunda yanlış bir şey yoktur, ancak bu diziyi kullanmanın niyeti onu 'string' (char *) olarak kullanmak olduğundan, bu diziyi değiştirmemelisiniz unutmak kolaydır.


3
Yanlış. Başlatma, dizgenin değişmezlerini diziye kopyalar. Dizi nesnesi, constbu şekilde tanımlanmadıkça değildir . (Ve dize değişmezleri, dize değişmezini constdeğiştirme girişiminin tanımsız davranışı olmasına rağmen değildir .) char *s = "literal";Bahsettiğiniz davranış türüne sahip değildir; olarak yazılmış daha iyiconst char *s = "literal";
Keith Thompson

gerçekten benim hatam, char [] 'ı char * ile karıştırdım. Ancak içeriği diziye kopyalamaktan pek emin olmazdım. MS C derleyicisi ile hızlı kontrol, 'char c [] = "asdf";' const segmentinde 'string' yaratacak ve bu adresi array değişkenine atayacaktır. Bu aslında const char array'e yapılan ödevlerden kaçınmamın bir nedeni.
Dainius

Şüpheciyim Bu programı deneyin ve hangi sonucu aldığınızı bildirin.
Keith Thompson

2
"Ve genellikle" asdf "bir sabittir, bu yüzden const olarak bildirilmelidir." - Aynı akıl yürütme, bir çağrı için constde geçerlidir int n = 42;, çünkü 42bir sabittir.
Keith Thompson

1
Hangi makinede olduğunun önemi yok. Dil standardı cdeğiştirilebilir niteliktedir . Bu tam olarak 1 + 1değerlendirildiği gibi bir garantidir 2. Eğer ben bağlantılı programı yukarıda baskı dışında bir şey yok EFGH, bu bir uygun olmayan C uygulamasını gösterir.
Keith Thompson
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.