Neden karakter yerine C karakter değişmez değerleri var?


103

C ++ 'da sizeof('a') == sizeof(char) == 1. Bu sezgisel anlam ifade eder, çünkü 'a'bir karakter kelimesi kelimesine ve sizeof(char) == 1standart tarafından tanımlandığı gibi.

Ancak C'de sizeof('a') == sizeof(int). Yani, C karakter değişmezlerinin aslında tamsayı olduğu görülmektedir. Nedenini bilen var mı? Bu C tuhaflığından pek çok söz bulabiliyorum ama neden var olduğuna dair bir açıklama yok.


sizeof sadece bir bayt boyutunu döndürür, değil mi? Bir char ve int boyut olarak eşit değil mi?
Josh Smeaton

1
Bu muhtemelen derleyiciye (ve mimariye) bağlıdır. Ne kullandığını söylemek ister misin? Standart (en azından '89'a kadar) çok gevşekti.
dmckee --- eski moderatör kedicik

2
Hayır. bir karakter her zaman 1 bayt büyüklüğündedir, bu nedenle sizeof ('a') == 1 her zaman (c ++ 'da), bir int teorik olarak 1 boyutunda olabilir , ancak bu en az 16 bitlik bir bayt gerektirir ki bu pek olası değildir: ) böylece sizeof ( 'a') = sizeof (int) 'dir! çok çoğu uygulamalarında C ++ olasılıkla
Johannes Schaub - LITB

2
... C.'de her zaman yanlış olsa da
Johannes Schaub - litb

22
'a' C - dönemindeki bir int'dir. Oraya önce C geldi - Kuralları C koydu. C ++ kuralları değiştirdi. C ++ kurallarının daha mantıklı olduğunu iddia edebilirsiniz, ancak C kurallarını değiştirmenin yarardan çok zarar vereceğini, dolayısıyla C standart komitesi akıllıca buna dokunmadı.
Jonathan Leffler

Yanıtlar:


36

aynı konu üzerine tartışma

"Daha spesifik olarak, integral promosyonlar. K&R C'de, ilk önce int'e yükseltilmeden bir karakter değerini kullanmak neredeyse (?) İmkansızdı, bu nedenle ilk etapta karakteri sabit int yapmak bu adımı ortadan kaldırdı. Çok karakter vardı ve hala var 'abcd' gibi sabitler veya ancak çoğu bir int'e sığar. "


Çok karakterli sabitler, tek bir makinedeki derleyiciler arasında bile taşınabilir değildir (GCC, platformlar arasında kendi kendine tutarlı görünmesine rağmen). Bakınız: stackoverflow.com/questions/328215
Jonathan Leffler

8
Dikkat ediyorum a) Bu alıntıya atıfta bulunulmamış; alıntı yalnızca "Söz konusu konuyu tartışan geçmiş bir ileti dizisinde yayınlanan bu görüşe katılmıyor musunuz?" ... ve b) Bu çok saçma , çünkü chardeğişken int değildir, bu yüzden bir karakteri sabit yapmak özel bir durumdur. Ve onu teşvik olmadan karakter değeri kullanmak kolaydır: c1 = c2;. OTOH, c1 = 'x'aşağı doğru bir dönüşümdür. En önemlisi, sizeof(char) != sizeof('x')bu ciddi bir dil hatasıdır. Çok baytlı karakter sabitlerine gelince: nedeni bunlar, ancak eskimişler.
Jim Balter

27

Asıl soru "neden?"

Bunun nedeni, mevcut kodla geriye doğru uyumlu kalmaya çalışırken, gerçek bir karakterin tanımının gelişip değişmesidir.

Erken C'nin karanlık günlerinde hiç tip yoktu. C dilinde programlamayı ilk öğrendiğimde, türler tanıtılmıştı, ancak işlevlerin, arayan kişiye argüman türlerinin ne olduğunu söyleyecek prototipleri yoktu. Bunun yerine, parametre olarak geçirilen her şeyin ya bir int boyutunda (bu tüm işaretçileri içeriyordu) ya da bir double olacağı standartlaştırıldı.

Bu, işlevi yazarken, ikiye katlamayan tüm parametrelerin, onları nasıl bildirmiş olursanız olun, yığında ints olarak saklandığı ve derleyicinin bunu sizin için işlemek için işleve kod koyduğu anlamına geliyordu.

Bu, işleri biraz tutarsız hale getirdi, bu yüzden K&R ünlü kitabını yazdığında, bir karakterin sadece bir fonksiyon parametresi değil, herhangi bir ifadede her zaman bir int'e yükseltileceği kuralını koydular.

ANSI komitesi C'yi ilk kez standartlaştırdığında, bu kuralı değiştirdiler, öyle ki bir karakter değişmezi basitçe bir int olacak, çünkü bu aynı şeyi başarmanın daha basit bir yolu gibi görünüyordu.

C ++ tasarlanırken, tüm işlevlerin tam prototiplere sahip olması gerekiyordu (bu, evrensel olarak iyi uygulama olarak kabul edilmesine rağmen, C'de hala gerekli değildir). Bu nedenle, bir karakterin bir karakterde saklanabileceğine karar verildi. Bunun C ++ 'da avantajı, char parametresi olan bir işlev ile int parametresi olan bir işlevin farklı imzalara sahip olmasıdır. Bu avantaj C'de durum böyle değildir.

Bu yüzden farklılar. Evrim...


2
'Neden?' Cevabını verdiğim için benden +1. Ancak son ifadeye katılmıyorum - "Bunun C ++ 'da avantajı, char parametresi olan bir işlev ile int parametresi olan bir işlevin farklı imzalara sahip olmasıdır" - C ++' da 2 işlevin parametrelerine sahip olması hala mümkündür. aynı boyutta ve farklı imzalar, örneğin void f(unsigned char)Vs void f(signed char).
Peter K

3
@PeterK John bunu daha iyi ifade edebilirdi, ancak söylediği esasen doğru. C ++ 'daki değişikliğin motivasyonu şuydu, eğer yazarsanız f('a'), muhtemelen aşırı yük çözünürlüğünün f(char)bu çağrı için seçilmesini istersiniz f(int). Dediğiniz gibi göreceli boyutları intve charkonuyla ilgili değil.
zwol

21

C'deki bir karakterin int türünde olmasının belirli nedenlerini bilmiyorum. Ancak C ++ 'da bu şekilde gitmemek için iyi bir neden var. Bunu düşün:

void print(int);
void print(char);

print('a');

Yazdırmak için yapılan çağrının bir karakter alarak ikinci versiyonu seçmesini beklersiniz. Tam anlamıyla bir karaktere sahip olmak, bunu imkansız kılar. Birden fazla karaktere sahip olan C ++ değişmez değerlerinde, değerleri uygulama tanımlı olmasına rağmen int türüne sahip olduğuna dikkat edin. Yani, 'ab'türü vardır intederken, 'a'türü vardır char.


Evet, "C ++ 'ın Tasarımı ve Gelişimi", aşırı yüklenmiş girdi / çıktı yordamlarının C ++' nın kuralları değiştirmesinin ana nedeni olduğunu söylüyor.
Max Lybbert

5
Max, evet hile yaptım. uyumluluk bölümünde standarda baktım :)
Johannes Schaub - litb

18

MacBook'umda gcc kullanarak şunu deniyorum:

#include <stdio.h>
#define test(A) do{printf(#A":\t%i\n",sizeof(A));}while(0)
int main(void){
  test('a');
  test("a");
  test("");
  test(char);
  test(short);
  test(int);
  test(long);
  test((char)0x0);
  test((short)0x0);
  test((int)0x0);
  test((long)0x0);
  return 0;
};

çalıştırıldığında şunu verir:

'a':    4
"a":    2
"":     1
char:   1
short:  2
int:    4
long:   4
(char)0x0:      1
(short)0x0:     2
(int)0x0:       4
(long)0x0:      4

bu, şüphelendiğiniz gibi bir karakterin 8 bit olduğunu, ancak bir karakterin bir int olduğunu gösterir.


7
İlginç olduğu için +1. İnsanlar genellikle sizeof ("a") ve sizeof ("") 'un char *' ler olduğunu ve 4 (veya 8) vermesi gerektiğini düşünürler. Ama aslında o noktada char [] 'lardır (sizeof (char [11]) 11 verir). Yeni başlayanlar için bir tuzak.
paxdiablo

3
Bir karakter değişmezi bir int'e yükseltilmez, zaten bir int'dir. Nesne sizeof operatörünün bir işleneni ise herhangi bir ilerleme yoktur. Olsaydı, bu sizeof'un amacını bozardı.
Chris Young

@Chris Young: Ya. Kontrol. Teşekkürler.
dmckee --- eski moderatör kedicik

8

C yazılırken, PDP-11'in MACRO-11 montaj dili şu özelliklere sahipti:

MOV #'A, R0      // 8-bit character encoding for 'A' into 16 bit register

Bu tür şeyler, assembly dilinde oldukça yaygındır - düşük 8 bit karakter kodunu tutacaktır, diğer bitler 0'a temizlenir. PDP-11 bile şunlara sahipti:

MOV #"AB, R0     // 16-bit character encoding for 'A' (low byte) and 'B'

Bu, 16 bitlik yazmacın düşük ve yüksek baytlarına iki karakter yüklemek için uygun bir yol sağladı. Daha sonra bunları başka bir yere yazabilir, bazı metin verilerini veya ekran hafızasını güncelleyebilirsiniz.

Bu nedenle, karakterlerin kayıt boyutuna yükseltilmesi fikri oldukça normal ve arzu edilir bir durumdur. Ancak, sabit kodlu işlem kodunun bir parçası olarak değil, ana bellekte aşağıdakileri içeren bir yerden 'A'yı kaydetmeniz gerektiğini varsayalım:

address: value
20: 'X'
21: 'A'
22: 'A'
23: 'X'
24: 0
25: 'A'
26: 'A'
27: 0
28: 'A'

Bu ana bellekten bir kütüğe sadece bir 'A' okumak isterseniz, hangisini okurdunuz?

  • Bazı CPU'lar yalnızca 16 bitlik bir değeri 16 bitlik bir sicile okumayı doğrudan destekleyebilir; bu, 20 veya 22'de bir okuma, daha sonra 'X' bitlerinin silinmesini gerektirecek ve CPU'nun sonluluğuna bağlı olarak bir veya diğerine bağlı olacaktır. düşük sıralı bayta geçilmesi gerekir.

  • Bazı CPU'lar belleğe göre ayarlanmış bir okuma gerektirebilir, bu da ilgili en düşük adresin veri boyutunun katı olması gerektiği anlamına gelir: 24 ve 25 adreslerinden okuyabilirsiniz, ancak 27 ve 28'den okuyamazsınız.

Bu nedenle, kayda bir 'A' almak için kod üreten bir derleyici, biraz fazladan bellek harcamayı ve değeri sonlanmaya bağlı olarak 0 'A' veya 'A' 0 olarak kodlamayı ve aynı zamanda doğru şekilde hizalandığından emin olmayı tercih edebilir ( yani tek bir hafıza adresinde değil).

Tahminimce, C'nin bu CPU-merkezli davranışı basitçe, bellek kayıt boyutlarını işgal eden karakter sabitlerini düşünerek, C'nin ortak değerlendirmesini "yüksek seviyeli birleştirici" olarak öne sürerek taşıdıklarıdır.

( Http://www.dmv.net/dec/pdf/macro.pdf sayfa 6-25'teki 6.3.3'e bakın )


5

K&R okuduğumu ve EOF'ye ulaşana kadar her seferinde bir karakteri okuyacak bir kod parçası gördüğümü hatırlıyorum. Tüm karakterler bir dosya / girdi akışında olmak için geçerli karakterler olduğundan, bu EOF'nin herhangi bir karakter değeri olamayacağı anlamına gelir. Kodun yaptığı şey, okuma karakterini bir int'e koymak, ardından EOF için test etmek, sonra değilse bir karaktere dönüştürmekti.

Bunun sorunuzu tam olarak yanıtlamadığını anlıyorum, ancak karakter değişmezlerinin geri kalanının sizeof (int) EOF literali olsaydı bir anlam ifade ederdi.

int r;
char buffer[1024], *p; // don't use in production - buffer overflow likely
p = buffer;

while ((r = getc(file)) != EOF)
{
  *(p++) = (char) r;
}

0'ın geçerli bir karakter olduğunu sanmıyorum.
gbjbaanb

3
@gbjbaanb: Elbette öyle. Bu boş karakterdir. Bunu düşün. Bir dosyanın sıfır bayt içermesine izin verilmemesi gerektiğini düşünüyor musunuz?
P Baba

1
Wikipedia'yı okuyun - "EOF'nin gerçek değeri, sisteme bağlı bir negatif sayıdır, genellikle -1'dir ve herhangi bir geçerli karakter koduyla eşit olmadığı garanti edilir."
Malx

2
Malx'ın dediği gibi - EOF bir karakter türü değildir - bu bir int türüdür. getchar () ve arkadaşları, herhangi bir karakterin yanı sıra EOF'yi de çakışmadan tutabilen bir int döndürür. Bu, gerçek karakterlerin int türüne sahip olmasını gerektirmez.
Michael Burr

2
EOF == -1, C'nin karakter sabitlerinden çok sonra geldi, bu yüzden bu bir cevap değil ve hatta alakalı bile değil.
Jim Balter

5

Bunun için bir mantık görmedim (C harfleri int tipleridir), ancak işte Stroustrup'un bu konuda söylemesi gereken bir şey var (Design and Evolution 11.2.1 - Fine-Grain Resolution):

C'de 'a'olduğu gibi değişmez bir karakterin türü int. Şaşırtıcı bir şekilde, verme 'a'tipi charC ++ herhangi bir uyumluluk sorun teşkil etmez. Patolojik örnek dışında sizeof('a'), hem C hem de C ++ 'da ifade edilebilen her yapı aynı sonucu verir.

Yani çoğu zaman hiçbir soruna yol açmamalı.


İlginç! Bir çeşit, C standartları komitesinin bu
tuhaflığı

2

Bunun tarihsel nedeni, C ve onun selefi B'nin başlangıçta 8-bit ASCII'yi destekleyen, ancak kayıtlarda yalnızca aritmetik gerçekleştirebilen çeşitli kelime büyüklüklerine sahip çeşitli DEC PDP mini bilgisayar modelleri üzerinde geliştirilmiş olmasıdır. (PDP-11 değil, ancak daha sonra geldi.) C'nin erken sürümleri int, makinenin yerel kelime boyutu ve bir işleve veya bir işleve aktarılmak üzere intgenişletilmesi gerekenden daha küçük herhangi bir değer olarak tanımlandı . intveya bitsel, mantıksal veya aritmetik bir ifadede kullanılır, çünkü temeldeki donanım bu şekilde çalışıyordu.

Tamsayı terfi kurallarının hala an'dan daha küçük herhangi bir veri türünün intyükseltildiğini söylemesinin nedeni de budur int. C uygulamalarının da benzer tarihsel nedenlerden dolayı ikinin tümleyicisi yerine bir tamamlayıcı matematiği kullanmasına izin verilir. Sekizlik karakter kaçışlarının ve sekizlik sabitlerin onaltılık ile karşılaştırıldığında birinci sınıf vatandaşlar olmalarının nedeni, aynı şekilde, bu erken DEC mini bilgisayarlarının üç baytlık parçalara bölünebilen, ancak dört baytlık atlamalara bölünemeyen sözcük boyutlarına sahip olmasıdır.


... ve chartam olarak 3 sekizlik rakam uzunluğundaydı
Antti Haapala

1

Bu, "entegre terfi" adı verilen doğru davranıştır. Diğer durumlarda da olabilir (doğru hatırlıyorsam çoğunlukla ikili operatörler).

DÜZENLEME: Emin olmak için, Expert C Programming: Deep Secrets kopyamı kontrol ettim ve bir char literalinin int tipiyle başlamadığını doğruladım . Bu tip başlangıçta olduğu kömürün ancak bir kullanıldığında ifadesi , bu olduğu terfi bir etmek int . Aşağıdakiler kitaptan alıntılanmıştır:

Karakter değişmezleri int türüne sahiptir ve oraya char türünden yükseltme kurallarını izleyerek ulaşırlar. Bu, sayfa 39'daki K&R 1'de çok kısaca ele alınmıştır:

İfadedeki her karakter int'e dönüştürülür .... Bir ifadedeki tüm float değerlerinin double'a dönüştürüldüğüne dikkat edin .... Bir işlev bağımsız değişkeni bir ifade olduğundan, tür dönüştürmeleri, işlevlere bağımsız değişkenler iletildiğinde de gerçekleşir: özellikle, char ve kısa int olur, float çift olur.


Diğer yorumlara inanılacaksa, 'a' ifadesi int türünde başlar - sizeof () içinde tür yükseltmesi gerçekleştirilmez. 'A' türünün int türüne sahip olması C'nin bir tuhaflığı gibi görünüyor.
j_random_hacker

2
Bir karakter değişmezi yapar tipi int var. ANSI / ISO 99 standardı bunları 'tam sayı karakter sabitleri' olarak adlandırır (bunları wchar_t türüne sahip 'geniş karakter sabitlerinden' ayırmak için) ve özellikle "Bir tamsayı karakter sabitinin türü int vardır" der.
Michael Burr

Demek istediğim, bunun int tipiyle başlamaması , bunun yerine char'dan int türüne dönüştürülmesiydi (yanıt düzenlendi). Elbette, dönüştürme her zaman yapıldığı için bu muhtemelen derleyici yazarları dışında kimseyi ilgilendirmez.
PolyThinker

3
Hayır! Eğer varsa ANSI / ISO 99 C standardını okumak Eğer C, ifade 'a' de olduğunu göreceksiniz ile başlar türü int. Eğer bir fonksiyon void f (int) ve değişken karakter c, ardından (c) f varsa olacak ( 'a') ayrılmaz promosyon gerçekleştirmek, ancak f olmaz türü olarak 'a' zaten int. Garip ama gerçek.
j_random_hacker

2
"Emin olmak için" - Şu ifadeyi gerçekten okuyarak daha emin olabilirsiniz: "Karakter değişmezleri int türüne sahiptir". "Bunun sadece sessiz değişikliklerden biri olduğunu varsayabilirim" - yanlış varsayıyorsunuz. C'deki karakter değişmezleri her zaman int türünde olmuştur.
Jim Balter

0

Bilmiyorum, ama bunu bu şekilde uygulamanın daha kolay olduğunu tahmin edeceğim ve gerçekten önemli değildi. Türün hangi işlevin çağrılıp düzeltilmesi gerektiğini belirleyebilmesi C ++ 'ya kadar değildi.


0

Bunu gerçekten bilmiyordum. Prototipler var olmadan önce, int'ten daha dar olan her şey, işlev argümanı olarak kullanıldığında int'e dönüştürülürdü. Bu açıklamanın bir parçası olabilir.


1
Başka bir zayıf "cevap". Otomatik dönüşüm chariçin intoldukça kılacak gereksiz karakter sabitleri int olmak için. Konuyla ilgili olan şey, dilin karakter sabitlerini değişkenlerden farklı şekilde (onlara farklı bir tür vererek) ele almasıdır charve ihtiyaç duyulan şey bu farkın açıklamasıdır.
Jim Balter

Aşağıda verdiğiniz açıklama için teşekkürler. Açıklamanızı, ait olduğu, oylanabildiği ve ziyaretçiler tarafından kolayca görülebildiği bir yanıtta daha eksiksiz bir şekilde açıklamak isteyebilirsiniz. Ayrıca, burada iyi bir cevabım olduğunu hiç söylemedim. Bu nedenle değer yargınızın hiçbir faydası yok.
Blaisorblade

0

Bu sadece dil spesifikasyonuna teğetseldir, ancak donanımda CPU genellikle sadece bir yazmaç boyutuna sahiptir - diyelim ki 32 bit - ve bu yüzden gerçekten bir karakter üzerinde çalıştığında (ekleyerek, çıkararak veya karşılaştırarak) kayda yüklendiğinde int'e örtük bir dönüşüm. Derleyici, her işlemden sonra sayıyı düzgün bir şekilde maskelemek ve kaydırmakla ilgilenir, böylece 2'ye (işaretsiz karakter) 254 eklerseniz, 256 yerine 0'a sarılır, ancak silikonun içinde gerçekten bir int olur. hafızaya geri kaydedene kadar.

Bu biraz akademik bir nokta çünkü dil zaten 8 bitlik bir edebi tür belirtmiş olabilir, ancak bu durumda dil spesifikasyonu CPU'nun gerçekte ne yaptığını daha yakından yansıtıyor.

(x86 wonks, örneğin kısa geniş yazmaçları bir adımda ekleyen yerel bir eklenti işleminin olduğunu not edebilir , ancak RISC çekirdeğinin içinde bu iki adıma dönüşür: sayıları ekleyin, ardından işaretini genişletme, ekleme / çıkarma çifti gibi PowerPC)


1
Yine başka bir yanlış cevap. Buradaki sorun, karakter değişmezlerinin ve chardeğişkenlerinin neden farklı türlere sahip olduğudur . Donanımı yansıtan otomatik promosyonlar alakalı değildir - aslında alakasızdır, çünkü chardeğişkenler otomatik olarak yükseltilir, bu yüzden karakter değişmezlerinin aynı tipte olmaması için bir neden yoktur char. Gerçek neden, artık kullanılmayan çok baytlı değişmez değerlerdir.
Jim Balter

@Jim Balter Multibyte değişmez değerleri artık eskimiş değil; çok baytlı Unicode ve UTF karakterleri var.
Crashworks

@Crashworks Çok baytlı karakter dizilerinden değil, çok baytlı karakter değişmezlerinden bahsediyoruz . Dikkat etmeye çalışın.
Jim Balter

4
Chrashworks karakterler yazdı . Geniş karakterli değişmez değerlerin (L'à 'diyelim) daha fazla bayt aldığını ancak çok baytlı karakter değişmezleri olarak adlandırılmadığını yazmış olmalısınız . Daha az kibirli olmak, kendiniz daha doğru olmanıza yardımcı olur.
Blaisorblade

@Blaisorblade Geniş karakter değişmezleri burada geçerli değil - yazdıklarımla hiçbir ilgisi yok. Doğruydum ve anlayamıyorsunuz ve beni düzeltmeye yönelik sahte girişiminiz kibirli olan şey
Jim Balter
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.