Tại sao các ký tự C là ints thay vì ký tự?


103

Trong C ++ sizeof('a') == sizeof(char) == 1,. Điều này có ý nghĩa trực quan, vì 'a'là một ký tự theo nghĩa đen, và sizeof(char) == 1như được định nghĩa bởi tiêu chuẩn.

Tuy nhiên, trong C sizeof('a') == sizeof(int),. Có nghĩa là, có vẻ như các ký tự C thực sự là số nguyên. Có ai biết tại sao không? Tôi có thể tìm thấy rất nhiều đề cập về câu hỏi C này nhưng không có lời giải thích tại sao nó tồn tại.


sizeof sẽ chỉ trả về kích thước của một byte phải không? Không phải là một char và một int có kích thước bằng nhau?
Josh Smeaton

1
Điều này có thể phụ thuộc vào trình biên dịch (và kiến ​​trúc). Bạn muốn nói những gì bạn đang sử dụng? Tiêu chuẩn (ít nhất là đến năm 89) rất lỏng lẻo.
dmckee --- ex-moderator kitten

2
Không. một char luôn lớn 1 byte, vì vậy sizeof ('a') == 1 luôn luôn (trong c ++), trong khi một int về mặt lý thuyết có thể là sizeof là 1, nhưng điều đó sẽ yêu cầu một byte có ít nhất 16bits, điều này rất khó xảy ra: ) để sizeof ( 'a') = sizeof (int) là! rất có khả năng trong C ++ trong hầu hết các trường
Johannes Schaub - litb

2
... trong khi nó luôn luôn sai trong C.
Johannes Schaub - litb

22
'a' là một số nguyên trong C - dấu chấm. C đến đó trước - C thực hiện các quy tắc. C ++ đã thay đổi các quy tắc. Bạn có thể tranh luận rằng các quy tắc C ++ có ý nghĩa hơn, nhưng việc thay đổi các quy tắc C sẽ gây hại nhiều hơn là có lợi, vì vậy ủy ban tiêu chuẩn C đã khôn ngoan không đề cập đến điều này.
Jonathan Leffler

Câu trả lời:


36

thảo luận về cùng một chủ đề

"Cụ thể hơn là các quảng cáo tích hợp. Trong K&R C, hầu như (?) Không thể sử dụng một giá trị ký tự mà nó không được thăng cấp thành int trước, vì vậy việc đặt ký tự không đổi int ngay từ đầu đã loại bỏ bước đó. Đã có và vẫn có nhiều ký tự các hằng số chẳng hạn như 'abcd' hoặc tuy nhiên nhiều hằng số sẽ phù hợp với một số nguyên. "


Các hằng số nhiều ký tự không có tính di động, ngay cả giữa các trình biên dịch trên một máy (mặc dù GCC dường như tự nhất quán trên các nền tảng). Xem: stackoverflow.com/questions/328215
Jonathan Leffler,

8
Tôi xin lưu ý rằng a) Trích dẫn này không được phân bổ; trích dẫn chỉ nói "Bạn có không đồng ý với ý kiến ​​này, đã được đăng trong một chủ đề trước đây thảo luận về vấn đề được đề cập không?" ... và b) Thật là lố bịch , bởi vì một charbiến không phải là int, vì vậy việc biến một ký tự thành một hằng số là một trường hợp đặc biệt. Và thật dễ dàng để sử dụng một giá trị nhân vật mà không thúc đẩy nó: c1 = c2;. OTOH, c1 = 'x'là một chuyển đổi đi xuống. Quan trọng nhất, sizeof(char) != sizeof('x')đó là một botch ngôn ngữ nghiêm trọng. Đối với hằng số ký tự nhiều byte: chúng là lý do, nhưng chúng đã lỗi thời.
Jim Balter

27

Câu hỏi ban đầu là "tại sao?"

Lý do là định nghĩa của một ký tự chữ đã phát triển và thay đổi, trong khi cố gắng duy trì tương thích ngược với mã hiện có.

Trong những ngày đen tối của đầu C không có loại nào cả. Vào lần đầu tiên tôi học lập trình bằng C, các kiểu đã được giới thiệu, nhưng các hàm không có nguyên mẫu để cho người gọi biết kiểu đối số là gì. Thay vào đó, nó được tiêu chuẩn hóa rằng mọi thứ được truyền dưới dạng tham số sẽ có kích thước của một int (điều này bao gồm tất cả các con trỏ) hoặc nó sẽ là một đôi.

Điều này có nghĩa là khi bạn viết hàm, tất cả các tham số không phải là đôi được lưu trữ trên ngăn xếp dưới dạng int, bất kể bạn khai báo chúng như thế nào và trình biên dịch đặt mã vào hàm để xử lý điều này cho bạn.

Điều này khiến mọi thứ hơi mâu thuẫn, vì vậy khi K&R viết cuốn sách nổi tiếng của họ, họ đã đưa ra quy tắc rằng một ký tự chữ sẽ luôn được thăng cấp thành một int trong bất kỳ biểu thức nào, không chỉ là một tham số hàm.

Khi ủy ban ANSI lần đầu tiên tiêu chuẩn hóa C, họ đã thay đổi quy tắc này để một ký tự chữ đơn giản chỉ là một số nguyên, vì đây có vẻ là một cách đơn giản hơn để đạt được điều tương tự.

Khi C ++ được thiết kế, tất cả các hàm được yêu cầu phải có nguyên mẫu đầy đủ (điều này vẫn không bắt buộc trong C, mặc dù nó được chấp nhận rộng rãi như một phương pháp tốt). Do đó, người ta quyết định rằng một ký tự có thể được lưu trữ trong một ký tự. Ưu điểm của điều này trong C ++ là một hàm có tham số char và một hàm với tham số int có các chữ ký khác nhau. Ưu điểm này không có trong C.

Đây là lý do tại sao chúng khác nhau. Sự phát triển...


2
+1 từ tôi vì đã thực sự trả lời 'tại sao?'. Nhưng tôi không đồng ý với câu cuối cùng - "Ưu điểm của điều này trong C ++ là một hàm có tham số char và một hàm với tham số int có chữ ký khác nhau" - trong C ++ vẫn có thể cho 2 hàm có tham số là cùng kích thước và chữ ký khác nhau, ví dụ như void f(unsigned char)Vs void f(signed char).
Peter K

3
@PeterK John có thể nói tốt hơn, nhưng những gì anh ấy nói về cơ bản là chính xác. Động lực cho sự thay đổi trong C ++ là, nếu bạn viết f('a'), bạn có thể muốn giải quyết quá tải để chọn f(char)cho cuộc gọi đó hơn là f(int). Các kích thước tương đối của intcharkhông liên quan, như bạn nói.
zwol

21

Tôi không biết lý do cụ thể tại sao một ký tự trong C thuộc loại int. Nhưng trong C ++, có một lý do chính đáng để không đi theo cách đó. Xem xét điều này:

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

print('a');

Bạn sẽ mong đợi rằng lệnh in sẽ chọn phiên bản thứ hai lấy một ký tự. Có một ký tự theo nghĩa đen là một int sẽ làm cho điều đó không thể. Lưu ý rằng trong C ++ các ký tự có nhiều hơn một ký tự vẫn có kiểu int, mặc dù giá trị của chúng được xác định thực thi. Vì vậy, 'ab'có loại int, trong khi 'a'có loại char.


Có, "Design and Evolution of C ++" nói rằng các quy trình nhập / xuất quá tải là lý do chính khiến C ++ thay đổi các quy tắc.
Max Lybbert

5
Max, vâng, tôi đã lừa dối. tôi nhìn vào các tiêu chuẩn trong phần tương thích :)
Johannes Schaub - litb

18

bằng cách sử dụng gcc trên MacBook của mình, tôi thử:

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

mà khi chạy cho:

'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

điều này gợi ý rằng một ký tự là 8 bit, giống như bạn nghi ngờ, nhưng một ký tự theo nghĩa đen là một số nguyên.


7
+1 vì thú vị. Mọi người thường nghĩ rằng sizeof ("a") và sizeof ("") là char * và nên cho 4 (hoặc 8). Nhưng trên thực tế, chúng là của char [] tại điểm đó (sizeof (char [11]) cho 11). Một cái bẫy cho người mới.
paxdiablo

3
Một chữ ký tự không được thăng cấp thành một int, nó đã là một int. Không có sự thăng tiến nào xảy ra nếu đối tượng là một toán hạng của toán tử sizeof. Nếu có, điều này sẽ đánh bại mục đích của sizeof.
Chris Young

@Chris Young: Ya. Kiểm tra. Cảm ơn.
dmckee --- ex-moderator kitten

8

Trở lại khi C đang được viết, hợp ngữ MACRO-11 của PDP-11 có:

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

Loại điều này khá phổ biến trong hợp ngữ - 8 bit thấp sẽ giữ mã ký tự, các bit khác bị xóa thành 0. PDP-11 thậm chí còn có:

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

Điều này cung cấp một cách thuận tiện để tải hai ký tự vào byte thấp và byte cao của thanh ghi 16 bit. Sau đó, bạn có thể viết chúng ở nơi khác, cập nhật một số dữ liệu văn bản hoặc bộ nhớ màn hình.

Vậy nên, việc các nhân vật được thăng cấp đăng ký size là điều khá bình thường và đáng mong đợi. Tuy nhiên, giả sử bạn cần đưa 'A' vào thanh ghi không phải là một phần của opcode được mã hóa cứng, mà từ một nơi nào đó trong bộ nhớ chính có chứa:

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

Nếu bạn chỉ muốn đọc một chữ 'A' từ bộ nhớ chính này vào một thanh ghi, bạn sẽ đọc cái nào?

  • Một số CPU có thể chỉ hỗ trợ trực tiếp đọc giá trị 16 bit vào thanh ghi 16 bit, có nghĩa là việc đọc ở 20 hoặc 22 sau đó sẽ yêu cầu các bit từ 'X' được xóa và tùy thuộc vào khả năng sử dụng của CPU này hay khác sẽ cần chuyển sang byte thứ tự thấp.

  • Một số CPU có thể yêu cầu đọc căn chỉnh bộ nhớ, có nghĩa là địa chỉ thấp nhất liên quan phải là bội số của kích thước dữ liệu: bạn có thể đọc từ địa chỉ 24 và 25, nhưng không đọc được từ địa chỉ 27 và 28.

Vì vậy, một trình biên dịch tạo mã để lấy một chữ 'A' vào thanh ghi có thể thích lãng phí thêm một chút bộ nhớ và mã hóa giá trị thành 0 'A' hoặc 'A' 0 - tùy thuộc vào endianness và cũng đảm bảo nó được căn chỉnh đúng cách ( tức là không ở một địa chỉ bộ nhớ lẻ).

Tôi đoán là C chỉ đơn giản là thực hiện hành vi tập trung vào CPU ở mức độ này, nghĩ đến các hằng số ký tự chiếm các kích thước thanh ghi của bộ nhớ, dẫn đến đánh giá chung về C là "trình hợp dịch cấp cao".

(Xem 6.3.3 trên trang 6-25 của http://www.dmv.net/dec/pdf/macro.pdf )


5

Tôi nhớ đã đọc K&R và thấy một đoạn mã sẽ đọc một ký tự tại một thời điểm cho đến khi nó chạm EOF. Vì tất cả các ký tự đều là các ký tự hợp lệ trong một tệp / luồng đầu vào, điều này có nghĩa là EOF không thể là bất kỳ giá trị char nào. Những gì mã đã làm là đặt ký tự đã đọc vào một int, sau đó kiểm tra EOF, sau đó chuyển đổi thành ký tự nếu không.

Tôi nhận ra rằng điều này không trả lời chính xác câu hỏi của bạn, nhưng nó sẽ có ý nghĩa đối với phần còn lại của các ký tự là sizeof (int) nếu ký tự EOF là.

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

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

Tuy nhiên, tôi không nghĩ 0 là một ký tự hợp lệ.
gbjbaanb

3
@gbjbaanb: Chắc chắn rồi. Đó là ký tự null. Hãy suy nghĩ về nó. Bạn có nghĩ rằng một tệp không được phép chứa bất kỳ byte nào không?
P Daddy

1
Đọc wikipedia - "Giá trị thực của EOF là số âm phụ thuộc vào hệ thống, thường là -1, được đảm bảo là không bằng với bất kỳ mã ký tự hợp lệ nào."
Malx

2
Như Malx nói - EOF không phải là một kiểu char - nó là một kiểu int. getchar () và bạn bè trả về một int, có thể giữ bất kỳ char nào cũng như EOF mà không có xung đột. Điều này thực sự sẽ không yêu cầu các ký tự chữ phải có kiểu int.
Michael Burr

2
EOF == -1 xuất hiện rất lâu sau hằng số ký tự của C, vì vậy đây không phải là câu trả lời và thậm chí không liên quan.
Jim Balter

5

Tôi chưa thấy lý do hợp lý cho nó (các chữ C char là kiểu int), nhưng đây là điều mà Stroustrup phải nói về nó (từ Design and Evolution 11.2.1 - Fine-Grain Resolution):

Trong C, loại ký tự theo nghĩa đen 'a'int. Đáng ngạc nhiên là việc 'a'nhập charvào C ++ không gây ra bất kỳ vấn đề tương thích nào. Ngoại trừ ví dụ bệnh lý sizeof('a'), mọi cấu trúc có thể được thể hiện bằng cả C và C ++ đều cho kết quả giống nhau.

Vì vậy, phần lớn, nó sẽ không gây ra vấn đề gì.


Hấp dẫn! Kinda mâu thuẫn với những gì người khác đang nói về cách thức ủy ban tiêu chuẩn C "một cách khôn ngoan" đã quyết định không để loại bỏ những đứa này từ C.
j_random_hacker

2

Lý do lịch sử cho điều này là C và người tiền nhiệm của nó là B, ban đầu được phát triển trên nhiều mẫu máy tính mini DEC PDP khác nhau với nhiều kích cỡ chữ khác nhau, hỗ trợ ASCII 8-bit nhưng chỉ có thể thực hiện số học trên thanh ghi. (Tuy nhiên, không phải PDP-11; nó ra đời sau.) Các phiên bản đầu tiên của C được xác định intlà kích thước từ gốc của máy và bất kỳ giá trị nào nhỏ hơn giá trị intcần được mở rộng intđể được chuyển đến hoặc từ một hàm , hoặc được sử dụng trong một biểu thức bit, logic hoặc số học, vì đó là cách phần cứng bên dưới hoạt động.

Đó cũng là lý do tại sao các quy tắc thăng hạng số nguyên vẫn nói rằng bất kỳ kiểu dữ liệu nào nhỏ hơn an intđều được thăng cấp int. Việc triển khai C cũng được phép sử dụng phép toán phần bù của một người thay vì phần bù của hai phần vì các lý do lịch sử tương tự. Lý do mà ký tự bát phân thoát ra và hằng số bát phân là công dân hạng nhất so với hệ lục phân cũng tương tự như vậy là vì những máy tính mini DEC đầu tiên đó có kích thước từ có thể chia thành các khối ba byte nhưng không phải là bốn byte.


... và dài charchính xác 3 chữ số bát phân
Antti Haapala 22/02/19

1

Đây là hành vi chính xác, được gọi là "khuyến mãi tích phân". Nó cũng có thể xảy ra trong các trường hợp khác (chủ yếu là toán tử nhị phân, nếu tôi nhớ không nhầm).

CHỈNH SỬA: Để chắc chắn, tôi đã kiểm tra bản sao Lập trình Expert C: Bí mật sâu sắc của mình và tôi xác nhận rằng một ký tự char không bắt đầu bằng kiểu int . Ban đầu nó có kiểu char nhưng khi nó được sử dụng trong một biểu thức , nó được thăng cấp thành int . Phần sau được trích từ cuốn sách:

Các chữ ký tự có kiểu int và chúng đến đó bằng cách tuân theo các quy tắc để thăng hạng từ kiểu char. Điều này được đề cập quá ngắn gọn trong K&R 1, trên trang 39, nơi có nội dung:

Mọi ký tự trong một biểu thức được chuyển đổi thành int .... Lưu ý rằng tất cả các số float trong một biểu thức đều được chuyển đổi thành double .... Vì đối số của hàm là một biểu thức, nên việc chuyển đổi kiểu cũng diễn ra khi các đối số được truyền cho các hàm: in đặc biệt, char và short trở thành int, float trở thành double.


Nếu các nhận xét khác được tin rằng, biểu thức 'a' bắt đầu bằng kiểu int - không có sự thăng hạng kiểu nào được thực hiện bên trong sizeof (). Có vẻ như 'a' có kiểu int chỉ là một sự khác biệt của C.
j_random_hacker

2
Một literal char không có kiểu int. Tiêu chuẩn ANSI / ISO 99 gọi chúng là 'hằng số ký tự số nguyên' (để phân biệt chúng với 'hằng số ký tự rộng', có kiểu wchar_t) và nói cụ thể, "Hằng số ký tự số nguyên có kiểu int."
Michael Burr

Ý tôi là nó không bắt đầu bằng kiểu int, mà được chuyển đổi thành int từ char (câu trả lời đã chỉnh sửa). Tất nhiên, điều này có lẽ không liên quan đến bất kỳ ai ngoại trừ người viết trình biên dịch vì quá trình chuyển đổi luôn được thực hiện.
PolyThinker

3
Không! Nếu bạn đọc tiêu chuẩn ANSI / ISO 99 C, bạn sẽ thấy rằng trong C, biểu thức 'a' bắt đầu bằng kiểu int. Nếu bạn có một hàm void f (int) và một biến char c, thì f (c) sẽ thực hiện thăng cấp tích phân, nhưng f ('a') thì không vì kiểu 'a' đã là int. Lạ nhưng có thật.
j_random_hacker

2
"Chỉ để chắc chắn" - Bạn có thể chắc chắn hơn bằng cách thực sự đọc câu lệnh: "Các chữ ký tự có kiểu int". "Tôi chỉ có thể cho rằng đó là một trong những thay đổi thầm lặng" - bạn giả định sai. Các ký tự trong C luôn có kiểu int.
Jim Balter

0

Tôi không biết, nhưng tôi đoán sẽ dễ dàng hơn để thực hiện nó theo cách đó và nó không thực sự quan trọng. Cho đến khi C ++ khi kiểu có thể xác định hàm nào sẽ được gọi thì nó mới cần được sửa.


0

Tôi không biết điều này thực sự. Trước khi các nguyên mẫu tồn tại, bất kỳ thứ gì hẹp hơn int đều được chuyển đổi thành int khi sử dụng nó làm đối số hàm. Đó có thể là một phần của lời giải thích.


1
Một "câu trả lời" kém cỏi khác. Tự động chuyển đổi charthành intsẽ làm cho các hằng số ký tự là số nguyên không cần thiết . Điều có liên quan là ngôn ngữ xử lý các hằng ký tự khác nhau (bằng cách đặt cho chúng một kiểu khác) với charcác biến và điều cần thiết là giải thích về sự khác biệt đó.
Jim Balter

Cảm ơn vì lời giải thích bạn đã đưa ra bên dưới. Bạn có thể muốn mô tả lời giải thích của mình đầy đủ hơn trong một câu trả lời, nơi nó thuộc về, có thể được bình chọn và khách truy cập dễ dàng nhìn thấy. Ngoài ra, tôi chưa bao giờ nói rằng tôi đã có một câu trả lời tốt ở đây. Do đó, phán đoán giá trị của bạn không có ích gì.
Blaisorblade

0

Điều này chỉ liên quan đến thông số ngôn ngữ, nhưng trong phần cứng, CPU thường chỉ có một kích thước thanh ghi - giả sử là 32 bit - và vì vậy bất cứ khi nào nó thực sự hoạt động trên một ký tự (bằng cách thêm, bớt hoặc so sánh nó) thì có một chuyển đổi ngầm định thành int khi nó được tải vào thanh ghi. Trình biên dịch sẽ xử lý đúng cách che dấu và chuyển số sau mỗi thao tác để nếu bạn thêm, chẳng hạn như 2 vào (unsigned char) 254, nó sẽ quấn quanh 0 thay vì 256, nhưng bên trong silicon nó thực sự là một int cho đến khi bạn lưu lại vào bộ nhớ.

Đó là một quan điểm học thuật bởi vì ngôn ngữ có thể đã chỉ định kiểu chữ 8-bit, nhưng trong trường hợp này, thông số ngôn ngữ sẽ phản ánh gần hơn những gì CPU đang thực sự làm.

(x86 wonks có thể lưu ý rằng có ví dụ: một addh op gốc bổ sung các thanh ghi rộng ngắn trong một bước, nhưng bên trong lõi RISC, điều này chuyển thành hai bước: thêm số, sau đó mở rộng dấu, giống như một cặp add / extsh trên PowerPC)


1
Lại một câu trả lời sai. Vấn đề ở đây là tại sao các ký tự và charbiến ký tự có các kiểu khác nhau. Quảng cáo tự động, phản ánh phần cứng, không có liên quan - chúng thực sự không liên quan, bởi vì charcác biến được tự động thăng hạng, vì vậy không có lý do gì để các ký tự không thuộc loại char. Lý do thực sự là các ký tự đa byte, hiện đã lỗi thời.
Jim Balter

@Jim Balter Chữ Multibyte không hề lỗi thời; có các ký tự Unicode và UTF nhiều byte.
Crashworks

@Crashworks Chúng ta đang nói về các ký tự nhiều byte , không phải các tự chuỗi nhiều byte . Cố gắng chú ý.
Jim Balter,

4
Chrashworks đã viết các ký tự . Bạn nên viết rằng các ký tự rộng (ví dụ như L'à ') chiếm nhiều byte hơn nhưng không được gọi là ký tự ký tự nhiều byte. Ít kiêu ngạo sẽ giúp bạn chính xác hơn về bản thân.
Blaisorblade

@Blaisorblade Các ký tự rộng không liên quan ở đây - chúng không liên quan gì đến những gì tôi đã viết. Tôi đã chính xác và bạn thiếu sự hiểu biết và nỗ lực không có thật của bạn để sửa chữa tôi là điều kiêu ngạo.
Jim Balter
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.