Mã nguồn này đang chuyển sang một chuỗi trong C. Nó làm điều đó như thế nào?


106

Tôi đang đọc qua một số mã giả lập và tôi đã phản bác lại điều gì đó thực sự kỳ quặc:

switch (reg){
    case 'eax':
    /* and so on*/
}

Sao có thể như thế được? Tôi nghĩ rằng bạn chỉ có thể switchtrên các loại tích phân. Có một số thủ thuật vĩ mô đang diễn ra không?


29
nó không phải là chuỗi 'eax'và nó liệt kê giá trị số nguyên liên tục
P__J__

12
Dấu ngoặc kép, không phải dấu ngoặc kép. Hằng số ký tự được thăng cấp int, vì vậy nó hợp pháp. Tuy nhiên, giá trị của một hằng số nhiều ký tự được xác định bởi việc triển khai, vì vậy mã có thể không hoạt động như mong đợi trên một trình biên dịch khác. Ví dụ, eaxcó thể là 0x65, 0x656178, 0x65617800, 0x786165, 0x6165, hay cái gì khác.
Davislor

2
@Davislor: với tên của biến là "reg" và thực tế là eax là một thanh ghi x86, tôi đoán rằng hành vi do triển khai xác định là ổn, vì nó giống nhau ở mọi nơi mà nó được sử dụng trong mã. 'eax' != 'ebx'Tất nhiên, chỉ miễn là , vì vậy nó chỉ thất bại một hoặc hai ví dụ của bạn. Mặc dù có thể có một số mã ở đâu đó giả định trên thực tế *(int*)("eax") == 'eax', và do đó hầu hết các ví dụ của bạn không thành công.
Steve Jessop

2
@SteveJessop Tôi không đồng ý với những gì bạn nói, nhưng có một nguy cơ thực sự là ai đó có thể cố gắng biên dịch mã trên một trình biên dịch khác, ngay cả cho cùng một kiến ​​trúc và có hành vi khác nhau. Ví dụ, 'eax'có thể so sánh bằng 'ebx'hoặc với 'ax', và câu lệnh switch sẽ không hoạt động như dự định.
Davislor

1
Tất cả bí ẩn đó sẽ nhanh chóng được xóa tan nếu bạn tra cứu / cho chúng tôi xem loại dữ liệu của reg.
ths

Câu trả lời:


146

(Chỉ bạn mới có thể trả lời phần "thủ thuật macro" - trừ khi bạn dán thêm mã. Nhưng ở đây không có nhiều thứ để macro hoạt động - về mặt chính thức, bạn không được phép xác định lại từ khóa ; hành vi thực hiện điều đó là không xác định.)

Để đạt được tính dễ đọc của chương trình, nhà phát triển dí dỏm đang khai thác hành vi triển khai được xác định . 'eax'không một chuỗi, nhưng một liên tục đa ký tự . Lưu ý rất cẩn thận các ký tự ngoặc kép xung quanh eax. Có nhiều khả năng nó mang lại cho bạn một inttrong trường hợp của bạn là duy nhất cho sự kết hợp của các ký tự. (Thường thì mỗi ký tự chiếm 8 bit trong 32 bit int). Và mọi người đều biết bạn có thể switchlàm được int!

Cuối cùng, một tham chiếu tiêu chuẩn:

Tiêu chuẩn C99 cho biết:

6.4.4.4p10: "Giá trị của một hằng số ký tự số nguyên chứa nhiều hơn một ký tự (ví dụ: 'ab'), hoặc chứa một ký tự hoặc chuỗi thoát không ánh xạ đến ký tự thực thi một byte, được xác định thực thi. "


55
Chỉ trong trường hợp bất kỳ ai nhìn thấy điều đó và hoảng sợ, "triển khai được xác định" bắt buộc phải hoạt động và được trình biên dịch của bạn ghi lại theo một số cách thích hợp (tiêu chuẩn không yêu cầu hành vi phải trực quan hoặc tài liệu phải tốt, nhưng ...). Điều này là "an toàn" để sử dụng cho một lập trình viên hoàn toàn hiểu những gì họ đang viết, trái ngược với "không xác định".
Leushenko

7
@Justin Mặc dù có thể, nhưng điều đó sẽ khá tệ. Nếu nó không làm những gì câu trả lời gợi ý là rất có thể, thì khả năng tiếp theo có lẽ là nó chỉ sử dụng ký tự đầu tiên và bỏ qua phần còn lại.
Barmar

5
@ZanLynx Tôi không tích cực, nhưng tôi tin rằng tính năng này có từ lâu trước Unicode và các tiêu chuẩn MBCS khác. "Những con số ma thuật" trông giống như văn bản trong các kết xuất bộ nhớ và ID phân đoạn tập tin định dạng theo kiểu RIFF là những ứng dụng đầu tiên tôi biết đến.
Russell Borogove

16
@ jpmc26 Đây không phải là hành vi không xác định, nó được xác định bởi việc triển khai. Vì vậy, trừ khi tài liệu trình biên dịch đề cập đến ma quỷ, mũi của bạn vẫn an toàn.
Barmar

7
@ZanLynx: Tôi e rằng mục đích ban đầu có trước Unicode, UTF-8 và bất kỳ mã hóa ký tự multibyte nào gần 20 năm. hằng số nhiều ký tự chỉ là một cách tiện dụng để biểu thị các số nguyên đại diện cho các nhóm 2, 3 hoặc 4 byte (tùy thuộc vào kích thước byte và int). Sự không nhất quán giữa các triển khai và kiến ​​trúc đã khiến ủy ban tuyên bố điều này là triển khai được định nghĩa , có nghĩa là không có cách nào di động để tính toán giá trị của 'ab'from 'a''b'.
chqrlie

45

Theo Tiêu chuẩn C (6.8.4.2 Câu lệnh chuyển đổi)

3 Biểu thức của mỗi nhãn trường hợp phải là một biểu thức hằng số nguyên ...

và (6.6 Biểu thức hằng số)

6 Một biểu thức hằng số nguyên phải có kiểu số nguyên và chỉ được có các toán hạng là hằng số nguyên, hằng số liệt kê, hằng số ký tự , biểu thức kích thước có kết quả là hằng số nguyên và hằng số động là toán hạng tức thì của phôi. Các toán tử truyền trong một biểu thức hằng số nguyên sẽ chỉ chuyển đổi kiểu số học sang kiểu số nguyên, ngoại trừ như một phần của toán hạng thành toán tử sizeof.

Bây giờ là gì 'eax' gì?

Tiêu chuẩn C (6.4.4.4 Hằng số ký tự)

2 Hằng số ký tự số nguyên là một chuỗi gồm một hoặc nhiều ký tự nhiều byte được đặt trong dấu ngoặc đơn , như trong 'x' ...

Như vậy 'eax'là một hằng số ký tự nguyên theo đoạn 10 của cùng một phần

  1. ... Giá trị của một hằng số ký tự nguyên có chứa nhiều hơn một ký tự (ví dụ: 'ab'), hoặc chứa một ký tự hoặc chuỗi thoát không ánh xạ đến ký tự thực thi một byte, được xác định thực thi.

Vì vậy, theo trích dẫn đầu tiên được đề cập, nó có thể là một toán hạng của một biểu thức hằng số nguyên có thể được sử dụng như một nhãn trường hợp.

Hãy chú ý rằng một hằng ký tự (đặt trong dấu ngoặc kép) có kiểu intvà không giống với một chuỗi ký tự (một chuỗi ký tự được đặt trong dấu ngoặc kép) có kiểu là một mảng ký tự.


12

Như người khác đã nói, đây là một int hằng số và giá trị thực tế của nó được xác định bởi việc triển khai.

Tôi cho rằng phần còn lại của mã trông giống như

if (SOMETHING)
    reg='eax';
...
switch (reg){
    case 'eax':
    /* and so on*/
}

Bạn có thể chắc chắn rằng 'eax' trong phần đầu tiên có cùng giá trị với 'eax' trong phần thứ hai, vì vậy tất cả đều hoạt động, phải không? ... Sai lầm.

Trong một nhận xét, @Davislor liệt kê một số giá trị có thể có cho 'eax':

... 0x65, 0x656178, 0x65617800, 0x786165, 0x6165, hay cái gì khác

Chú ý giá trị tiềm năng đầu tiên? Đó chỉ là 'e', bỏ qua hai nhân vật còn lại. Vấn đề là chương trình có thể sử dụng 'eax', 'ebx'v.v. Nếu tất cả các hằng số này có cùng giá trị 'e'mà bạn kết thúc với

switch (reg){
    case 'e':
       ...
    case 'e':
       ...
    ...
}

Điều này trông không quá tốt, phải không?

Phần tốt về "định nghĩa thực thi" là lập trình viên có thể kiểm tra tài liệu của trình biên dịch của họ và xem liệu nó có hoạt động hợp lý với các hằng số này hay không. Nếu có, nhà miễn phí.

Phần tồi tệ là một số người nghèo khác có thể lấy mã và cố gắng biên dịch nó bằng một số trình biên dịch khác. Lỗi biên dịch tức thì. Chương trình không phải là di động.

Như @zwol đã chỉ ra trong các bình luận, tình hình không hoàn toàn tệ như tôi nghĩ, trong trường hợp xấu là mã không biên dịch. Điều này ít nhất sẽ cung cấp cho bạn một tên tệp chính xác và số dòng cho sự cố. Tuy nhiên, bạn sẽ không có một chương trình làm việc.


1
khác hơn là một số hình thức assert('eax' != 'ebx'); //if this fails you can't compile the code because...là có bất cứ điều gì tác giả ban đầu có thể làm gì để ngăn ngừa các lỗi biên dịch khác mà không cần thay thế các cấu trúc hoàn toàn>
Dan là Fiddling Bằng ánh lửa

6
Hai nhãn trường hợp có cùng giá trị là một vi phạm ràng buộc (6.8.4.2p3: "... không có hai trong số các biểu thức hằng số trường hợp trong cùng một câu lệnh switch sẽ có cùng giá trị sau khi chuyển đổi") vì vậy, miễn là tất cả mã coi các giá trị của các hằng số này là không rõ ràng, điều này được đảm bảo hoạt động hoặc không biên dịch được.
zwol

Phần tồi tệ hơn là người nghèo biên dịch trên một trình biên dịch khác có thể sẽ không thấy bất kỳ lỗi thời gian biên dịch nào (chuyển đổi trên int cũng được); thay vào đó, thời gian chạy lỗi sẽ cắt lên ...
cá heo tucuxi

1

Đoạn mã sử dụng một giá trị kỳ lạ trong lịch sử được gọi là hằng số ký tự đa ký tự , còn được gọi là đa ký tự .

'eax' là một hằng số nguyên có giá trị được thực thi xác định.

Đây là một trang thú vị về nhiều ký tự và cách chúng có thể được sử dụng nhưng không nên:

http://www.zipcon.net/~swhite/docs/computers/languages/c_multi-char_const.html


Nhìn lại xa hơn vào gương chiếu hậu, đây là cách hướng dẫn sử dụng C gốc của Dennis Ritchie từ những ngày xưa ( https://www.bell-labs.com/usr/dmr/www/cman.pdf ) đã chỉ định hằng số ký tự .

2.3.2 Hằng số ký tự

Hằng số ký tự là 1 hoặc 2 ký tự được đặt trong dấu nháy đơn '' '''. Trong một hằng số ký tự, một dấu ngoặc kép phải được đặt trước bằng dấu gạch chéo ngược '' \''. Một số ký tự không phải đồ họa và \bản thân '' '' có thể được thoát theo bảng sau:

    BS \b
    NL \n
    CR \r
    HT \t
    ddd \ddd
    \ \\

Thoát '' \ddd'' bao gồm dấu gạch chéo ngược theo sau là 1, 2 hoặc 3 chữ số bát phân được sử dụng để chỉ định giá trị của ký tự mong muốn. Một trường hợp đặc biệt của công trình này là ''\0 '' (không theo sau bởi một chữ số) cho biết một ký tự rỗng.

Hằng ký tự hoạt động chính xác như số nguyên (đặc biệt, không giống như các đối tượng của kiểu ký tự). Phù hợp với cấu trúc định địa chỉ của PDP-11, hằng số ký tự có độ dài 1 có mã cho ký tự đã cho trong byte bậc thấp và 0 trong byte bậc cao; hằng số ký tự có độ dài 2 có mã cho ký tự đầu tiên trong byte thấp và mã cho ký tự thứ hai trong byte bậc cao. Các hằng số ký tự có nhiều hơn một ký tự vốn phụ thuộc vào máy móc và nên tránh.

Cụm từ cuối cùng là tất cả những gì bạn cần nhớ về cấu trúc gây tò mò này: Hằng số ký tự có nhiều hơn một ký tự vốn phụ thuộc vào máy móc và nên tránh.

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.