Mengapa literal karakter C int bukan karakter?

103

Di C ++ sizeof('a') == sizeof(char) == 1,. Ini masuk akal secara intuitif, karena 'a'merupakan karakter literal, dan sizeof(char) == 1seperti yang didefinisikan oleh standar.

Namun dalam C sizeof('a') == sizeof(int),. Artinya, tampaknya literal karakter C sebenarnya adalah bilangan bulat. Ada yang tahu kenapa? Saya dapat menemukan banyak penyebutan keanehan C ini tetapi tidak ada penjelasan mengapa hal itu ada.

Joseph Garvin
sumber
sizeof hanya akan mengembalikan ukuran byte bukan? Bukankah sebuah char dan int berukuran sama?
Josh Smeaton
1
Ini mungkin bergantung pada kompiler (dan arsitektur). Mau bilang apa yang Anda gunakan? Standar (setidaknya hingga '89) sangat longgar.
dmckee --- mantan moderator anak kucing
2
tidak. sebuah char selalu berukuran 1 byte, jadi sizeof ('a') == 1 always (dalam c ++), sedangkan int secara teoritis dapat berukuran 1, tetapi itu akan membutuhkan byte yang memiliki setidaknya 16 bit, yang sangat tidak mungkin: ) jadi sizeof ('a')! = sizeof (int) sangat mungkin di C ++ di sebagian besar implementasi
Johannes Schaub - litb
2
... sementara itu selalu salah di C.
Johannes Schaub - litb
22
'a' adalah int dalam C - periode. C sampai di sana dulu - C membuat aturannya. C ++ mengubah aturannya. Anda dapat berargumen bahwa aturan C ++ lebih masuk akal, tetapi mengubah aturan C akan melakukan lebih banyak kerusakan daripada kebaikan, jadi komite standar C dengan bijak belum menyentuh ini.
Jonathan Leffler

Jawaban:

36

diskusi tentang subjek yang sama

"Lebih khusus lagi promosi integral. Dalam K&R C hampir (?) Tidak mungkin menggunakan nilai karakter tanpa dipromosikan ke int terlebih dahulu, jadi membuat karakter konstan int di tempat pertama menghilangkan langkah itu. Ada dan masih ada multi karakter konstanta seperti 'abcd' atau berapa banyak pun yang akan muat dalam int. "

Malx
sumber
Konstanta multi-karakter tidak portabel, bahkan di antara kompiler pada satu mesin (meskipun GCC tampaknya konsisten di seluruh platform). Lihat: stackoverflow.com/questions/328215
Jonathan Leffler
8
Saya akan mencatat bahwa a) Kutipan ini tidak diberi atribut; kutipan hanya mengatakan "Apakah Anda tidak setuju dengan pendapat ini, yang diposting di utas sebelumnya yang membahas masalah yang dipermasalahkan?" ... dan b) Ini menggelikan , karena sebuah charvariabel bukan int, jadi membuat sebuah konstanta karakter menjadi satu adalah kasus khusus. Dan sangat mudah untuk menggunakan nilai karakter tanpa mempromosikannya: c1 = c2;. OTOH, c1 = 'x'adalah konversi ke bawah. Yang terpenting, sizeof(char) != sizeof('x')yang merupakan gangguan bahasa yang serius. Sedangkan untuk konstanta karakter multibyte: itulah alasannya, tapi sudah usang.
Jim Balter
27

Pertanyaan aslinya adalah "mengapa?"

Alasannya adalah bahwa definisi karakter literal telah berkembang dan berubah, sambil mencoba untuk tetap kompatibel dengan kode yang ada.

Di hari-hari gelap awal C tidak ada tipe sama sekali. Pada saat saya pertama kali belajar memprogram dalam C, tipe telah diperkenalkan, tetapi fungsi tidak memiliki prototipe untuk memberi tahu pemanggil apa tipe argumennya. Alih-alih itu distandarisasi bahwa semua yang dilewatkan sebagai parameter akan menjadi ukuran int (ini termasuk semua pointer) atau akan menjadi ganda.

Ini berarti bahwa ketika Anda menulis fungsi, semua parameter yang bukan double disimpan di stack sebagai int, tidak peduli bagaimana Anda mendeklarasikannya, dan kompilator meletakkan kode di dalam fungsi untuk menangani ini untuk Anda.

Ini membuat hal-hal menjadi agak tidak konsisten, jadi ketika K&R menulis buku terkenal mereka, mereka memasukkan aturan bahwa literal karakter akan selalu dipromosikan menjadi int dalam ekspresi apa pun, bukan hanya parameter fungsi.

Ketika komite ANSI pertama kali membuat standar C, mereka mengubah aturan ini sehingga karakter literal akan menjadi int, karena ini tampaknya cara yang lebih sederhana untuk mencapai hal yang sama.

Ketika C ++ sedang dirancang, semua fungsi harus memiliki prototipe lengkap (ini masih tidak diperlukan di C, meskipun diterima secara universal sebagai praktik yang baik). Karena itu, diputuskan bahwa literal karakter dapat disimpan dalam karakter. Keuntungan dari ini di C ++ adalah bahwa fungsi dengan parameter char dan fungsi dengan parameter int memiliki tanda tangan yang berbeda. Keuntungan ini tidak terjadi di C.

Inilah mengapa mereka berbeda. Evolusi...

John Vincent
sumber
2
+1 dari saya karena benar-benar menjawab 'mengapa?'. Tetapi saya tidak setuju dengan pernyataan terakhir - "Keuntungan dari ini di C ++ adalah bahwa fungsi dengan parameter char dan fungsi dengan parameter int memiliki tanda tangan yang berbeda" - di C ++ masih dimungkinkan untuk 2 fungsi memiliki parameter ukuran yang sama dan tanda tangan yang berbeda, misalnya void f(unsigned char)Vs void f(signed char).
Peter K
3
@PeterK John bisa menjelaskannya dengan lebih baik, tetapi apa yang dia katakan pada dasarnya akurat. Motivasi untuk mengubah C ++ adalah, jika Anda menulisf('a') , Anda mungkin ingin resolusi yang berlebihan untuk memilih f(char)panggilan itu daripada f(int). Ukuran relatif intdan chartidak relevan, seperti yang Anda katakan.
zwol
21

Saya tidak tahu alasan spesifik mengapa literal karakter di C adalah tipe int. Tapi di C ++, ada alasan bagus untuk tidak pergi ke sana. Pertimbangkan ini:

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

print('a');

Anda akan berharap bahwa panggilan untuk mencetak memilih versi kedua yang mengambil karakter. Memiliki karakter literal menjadi int akan membuat itu tidak mungkin. Perhatikan bahwa dalam literal C ++ yang memiliki lebih dari satu karakter masih memiliki tipe int, meskipun nilainya ditentukan dalam implementasi. Jadi, 'ab'punya tipe int, sementara 'a'punya tipe char.

Johannes Schaub - litb
sumber
Ya, "Desain dan Evolusi C ++" mengatakan bahwa rutinitas input / output yang kelebihan beban adalah alasan utama C ++ mengubah aturan.
Max Lybbert
5
Max, ya aku curang. saya melihat di standar di bagian kompatibilitas :)
Johannes Schaub - litb
18

menggunakan gcc di MacBook saya, saya mencoba:

#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;
};

yang ketika dijalankan memberi:

'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

yang menunjukkan bahwa karakter adalah 8 bit, seperti yang Anda duga, tetapi literal karakter adalah int.

dmckee --- mantan moderator anak kucing
sumber
7
1 karena menarik. Orang sering berpikir bahwa sizeof ("a") dan sizeof ("") adalah char * dan harus memberi 4 (atau 8). Tapi pada kenyataannya mereka adalah char [] pada saat itu (sizeof (char [11]) memberikan 11). Jebakan untuk pemula.
paxdiablo
3
Sebuah literal karakter tidak dipromosikan menjadi int, itu sudah menjadi int. Tidak ada promosi yang terjadi jika objek adalah operan sebesar operator. Jika ada, ini akan mengalahkan ukuran tujuan.
Chris Young
@Risyoung: Ya. Memeriksa. Terima kasih.
dmckee --- kucing bekas moderator
8

Kembali ketika C sedang ditulis, bahasa assembly MACRO-11 PDP-11 memiliki:

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

Hal semacam ini cukup umum dalam bahasa assembly - 8 bit rendah akan menampung kode karakter, bit lain dihapus ke 0. PDP-11 bahkan memiliki:

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

Ini memberikan cara yang mudah untuk memuat dua karakter ke dalam byte rendah dan tinggi dari register 16 bit. Anda kemudian dapat menulisnya di tempat lain, memperbarui beberapa data tekstual atau memori layar.

Jadi, gagasan tentang karakter yang dipromosikan ke ukuran register cukup normal dan diinginkan. Tapi, katakanlah Anda perlu memasukkan 'A' ke dalam register bukan sebagai bagian dari opcode hard-coded, tetapi dari suatu tempat di memori utama yang berisi:

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

Jika Anda hanya ingin membaca 'A' dari memori utama ini ke dalam register, mana yang akan Anda baca?

  • Beberapa CPU mungkin hanya secara langsung mendukung pembacaan nilai 16 bit ke dalam register 16 bit, yang berarti pembacaan pada 20 atau 22 kemudian akan membutuhkan bit dari 'X' dihapus, dan tergantung pada endian dari CPU satu atau lainnya perlu beralih ke byte orde rendah.

  • Beberapa CPU mungkin memerlukan pembacaan yang diselaraskan dengan memori, yang berarti bahwa alamat terendah yang terlibat harus kelipatan dari ukuran data: Anda mungkin dapat membaca dari alamat 24 dan 25, tetapi tidak 27 dan 28.

Jadi, kompiler yang menghasilkan kode untuk mendapatkan 'A' ke dalam register mungkin lebih suka membuang sedikit memori ekstra dan menyandikan nilainya sebagai 0 'A' atau 'A' 0 - tergantung pada endianness, dan juga memastikannya disejajarkan dengan benar ( yaitu tidak pada alamat memori yang aneh).

Dugaan saya adalah bahwa C hanya membawa tingkat perilaku CPU-sentris ini ke atas, memikirkan konstanta karakter yang menempati ukuran register memori, membawa penilaian umum C sebagai "assembler tingkat tinggi".

(Lihat 6.3.3 di halaman 6-25 dari http://www.dmv.net/dec/pdf/macro.pdf )

Tony Delroy
sumber
5

Saya ingat membaca K&R dan melihat potongan kode yang akan membaca karakter pada satu waktu sampai mencapai EOF. Karena semua karakter adalah karakter yang valid untuk berada dalam aliran file / input, ini berarti EOF tidak dapat berupa nilai karakter apa pun. Apa yang kode itu lakukan adalah memasukkan karakter yang sudah dibaca ke dalam int, lalu menguji EOF, lalu mengubahnya menjadi karakter jika tidak.

Saya menyadari ini tidak benar-benar menjawab pertanyaan Anda, tetapi akan masuk akal jika literal karakter lainnya menjadi sizeof (int) jika literal EOF adalah.

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

while ((r = getc(file)) != EOF)
{
  *(p++) = (char) r;
}
Kyle Cronin
sumber
Saya tidak berpikir 0 adalah karakter yang valid.
gbjbaanb
3
@gbjbaanb: Tentu saja. Ini karakter nol. Pikirkan tentang itu. Apakah menurut Anda file tidak boleh berisi nol byte?
P Daddy
1
Baca wikipedia - "Nilai sebenarnya dari EOF adalah angka negatif yang bergantung pada sistem, biasanya -1, yang dijamin tidak sama dengan kode karakter yang valid."
Malx
2
Seperti yang dikatakan Malx - EOF bukanlah tipe char - ini adalah tipe int. getchar () dan teman-teman mengembalikan sebuah int, yang dapat menampung karakter apa pun serta EOF tanpa konflik. Ini benar-benar tidak membutuhkan karakter literal untuk memiliki tipe int.
Michael Burr
2
EOF == -1 muncul jauh setelah konstanta karakter C, jadi ini bukan jawaban dan bahkan tidak relevan.
Jim Balter
5

Saya belum melihat alasan untuk itu (C char literals adalah tipe int), tetapi inilah yang dikatakan Stroustrup tentang hal itu (dari Design and Evolution 11.2.1 - Fine-Grain Resolution):

Di C, tipe karakter literal seperti 'a'adalah int. Anehnya, pemberian 'a'tipe chardalam C ++ tidak menyebabkan masalah kompatibilitas. Kecuali untuk contoh patologis sizeof('a'), setiap konstruksi yang dapat diekspresikan di C dan C ++ memberikan hasil yang sama.

Jadi untuk sebagian besar, itu seharusnya tidak menimbulkan masalah.

Michael Burr
sumber
Menarik! Agak bertentangan dengan apa yang dikatakan orang lain tentang bagaimana komite standar C "dengan bijaksana" memutuskan untuk tidak menghapus kekhasan ini dari C.
j_random_hacker
2

Alasan historis untuk ini adalah bahwa C, dan pendahulunya B, awalnya dikembangkan pada berbagai model minikomputer DEC PDP dengan berbagai ukuran kata, yang mendukung ASCII 8-bit tetapi hanya dapat melakukan aritmatika pada register. (Bukan PDP-11, bagaimanapun; itu datang kemudian.) Versi awal C didefinisikan intsebagai ukuran kata asli mesin, dan nilai apa pun yang lebih kecil dari yang intdiperlukan untuk dilebarkan keint agar dapat diteruskan ke atau dari suatu fungsi , atau digunakan dalam ekspresi bitwise, logis atau aritmatika, karena begitulah cara perangkat keras yang mendasarinya bekerja.

Itu juga mengapa aturan promosi bilangan bulat masih mengatakan bahwa tipe data apa pun yang lebih kecil dari satu intakan dipromosikan int. Implementasi C juga diperbolehkan untuk menggunakan matematika pelengkap satu daripada pelengkap dua untuk alasan historis yang serupa. Alasan pelolosan karakter oktal dan konstanta oktal adalah warga kelas satu dibandingkan dengan heksa juga karena minikomputer DEC awal memiliki ukuran kata yang dapat dibagi menjadi potongan tiga-byte tetapi bukan camilan empat-byte.

Davislor
sumber
... dan charpanjangnya tepat 3 angka oktal
Antti Haapala
1

Ini adalah perilaku yang benar, yang disebut "promosi integral". Ini dapat terjadi dalam kasus lain juga (terutama operator biner, jika saya ingat dengan benar).

EDIT: Hanya untuk memastikan, saya memeriksa salinan Expert C Programming: Deep Secrets , dan saya mengonfirmasi bahwa literal karakter tidak dimulai dengan tipe int . Ini awalnya bertipe char tetapi ketika digunakan dalam ekspresi , itu dipromosikan menjadi int . Berikut kutipan dari buku tersebut:

Literal karakter memiliki tipe int dan mereka mendapatkannya dengan mengikuti aturan untuk promosi dari tipe char. Ini terlalu singkat tercakup dalam K&R 1, di halaman 39 di mana dikatakan:

Setiap karakter dalam ekspresi diubah menjadi int .... Perhatikan bahwa semua float dalam ekspresi diubah menjadi ganda .... Karena argumen fungsi adalah ekspresi, konversi tipe juga terjadi saat argumen diteruskan ke fungsi: in partikular, char dan short menjadi int, float menjadi double.

PolyThinker
sumber
Jika komentar lain bisa dipercaya, ekspresi 'a' dimulai dengan tipe int - tidak ada promosi tipe yang dilakukan di dalam sizeof (). Itu 'a' memiliki tipe int hanyalah kekhasan dari C tampaknya.
j_random_hacker
2
Sebuah literal char memang memiliki tipe int. Standar ANSI / ISO 99 memanggilnya 'konstanta karakter integer' (untuk membedakannya dari 'konstanta karakter lebar', yang memiliki tipe wchar_t) dan secara khusus mengatakan, "Konstanta karakter integer memiliki tipe int."
Michael Burr
Yang saya maksud adalah bahwa itu tidak dimulai dengan tipe int, melainkan diubah menjadi int dari char (jawaban diedit). Tentu saja, ini mungkin tidak menjadi perhatian siapa pun kecuali penulis kompiler karena konversi selalu dilakukan.
PolyThinker
3
Tidak! Jika Anda membaca standar ANSI / ISO 99 C Anda akan menemukan bahwa di C, ekspresi 'a' dimulai dengan tipe int. Jika Anda memiliki fungsi void f (int) dan variabel char c, maka f (c) akan melakukan promosi integral, tetapi f ('a') tidak akan berfungsi karena jenis 'a' sudah menjadi int. Aneh tapi Nyata.
j_random_hacker
2
"Hanya untuk memastikan" - Anda bisa lebih yakin dengan membaca pernyataan: "Literal karakter memiliki tipe int". "Saya hanya bisa berasumsi bahwa itu adalah salah satu perubahan diam-diam" - Anda menganggapnya salah. Literal karakter di C selalu bertipe int.
Jim Balter
0

Saya tidak tahu, tapi saya akan menebak lebih mudah menerapkannya seperti itu dan itu tidak terlalu penting. Tidak sampai C ++ ketika tipe dapat menentukan fungsi mana yang akan dipanggil yang perlu diperbaiki.

Roland Rabien
sumber
0

Saya memang tidak tahu ini. Sebelum prototipe ada, sesuatu yang lebih sempit dari int diubah menjadi int saat menggunakannya sebagai argumen fungsi. Itu mungkin bagian dari penjelasannya.

Blaisorblade
sumber
1
"Jawaban" buruk lainnya. Konversi otomatis charke intakan membuat konstanta karakter tidak diperlukan menjadi int. Yang relevan adalah bahasa memperlakukan konstanta karakter secara berbeda (dengan memberinya tipe yang berbeda) dari charvariabel, dan yang dibutuhkan adalah penjelasan tentang perbedaan itu.
Jim Balter
Terima kasih atas penjelasan yang Anda berikan di bawah ini. Anda mungkin ingin mendeskripsikan penjelasan Anda lebih lengkap dalam jawaban, tempatnya, dapat dipilih secara maksimal, dan mudah dilihat oleh pengunjung. Juga, saya tidak pernah mengatakan saya punya jawaban yang bagus di sini. Oleh karena itu penilaian nilai Anda tidak membantu.
Blaisorblade
0

Ini hanya bersinggungan dengan spesifikasi bahasa, tetapi dalam perangkat keras CPU biasanya hanya memiliki satu ukuran register - 32 bit, katakanlah - dan kapan pun ia benar-benar berfungsi pada char (dengan menambahkan, mengurangi, atau membandingkannya) ada sebuah konversi implisit menjadi int ketika itu dimuat ke register. Kompilator menangani dengan benar masking dan menggeser nomor setelah setiap operasi sehingga jika Anda menambahkan, katakanlah, 2 ke (unsigned char) 254, itu akan membungkus ke 0 bukan 256, tetapi di dalam silikon itu benar-benar int sampai Anda menyimpannya kembali ke memori.

Ini semacam poin akademis karena bahasa tersebut bisa saja menentukan tipe literal 8-bit, tetapi dalam kasus ini spesifikasi bahasa mencerminkan lebih dekat apa yang sebenarnya dilakukan CPU.

(x86 wonks mungkin mencatat bahwa ada, misalnya, addh op asli yang menambahkan register lebar-pendek dalam satu langkah, tetapi di dalam inti RISC ini diterjemahkan menjadi dua langkah: tambahkan angka, lalu perpanjang tanda, seperti pasangan add / extsh pada PowerPC)

Crashworks
sumber
1
Namun jawaban salah lainnya. Masalahnya di sini adalah mengapa literal karakter dan charvariabel memiliki tipe yang berbeda. Promosi otomatis, yang mencerminkan perangkat keras, tidak relevan - sebenarnya anti-relevan, karena charvariabel secara otomatis dipromosikan sehingga bukan alasan literal karakter tidak bertipe char. Alasan sebenarnya adalah literal multibyte, yang sekarang sudah usang.
Jim Balter
Literal @Jim Balter Multibyte sama sekali tidak usang; ada multibyte Unicode dan karakter UTF.
Crashworks
@Crashworks Kita sedang berbicara tentang literal karakter multibyte , bukan literal string multibyte . Cobalah untuk memperhatikan.
Jim Balter
4
Chrashworks memang menulis karakter . Anda harus menulis bahwa literal karakter lebar (katakanlah L'à ') memang mengambil lebih banyak byte tetapi tidak disebut literal karakter multibyte. Menjadi kurang sombong akan membantu Anda menjadi lebih akurat.
Blaisorblade
@Blaisorblade Literal karakter lebar tidak relevan di sini - tidak ada hubungannya dengan apa yang saya tulis. Saya akurat dan Anda kurang memahami dan upaya palsu Anda untuk mengoreksi saya adalah hal yang sombong.
Jim Balter