Chuẩn C ++ có cho phép bool chưa khởi tạo làm hỏng chương trình không?

514
Remz 2019-01-10 15:39.

Tôi biết rằng một "hành vi không xác định" trong C ++ có thể cho phép trình biên dịch làm bất cứ điều gì nó muốn. Tuy nhiên, tôi đã gặp sự cố khiến tôi ngạc nhiên vì tôi cho rằng mã này đủ an toàn.

Trong trường hợp này, sự cố thực sự chỉ xảy ra trên một nền tảng cụ thể bằng cách sử dụng một trình biên dịch cụ thể và chỉ khi tối ưu hóa được bật.

Tôi đã thử một số cách để tái tạo vấn đề và đơn giản hóa nó đến mức tối đa. Đây là phần trích xuất của một hàm được gọi Serialize, sẽ nhận tham số bool và sao chép chuỗi truehoặc falsevào bộ đệm đích hiện có.

Liệu chức năng này có nằm trong quá trình xem xét mã không, sẽ không có cách nào để nói rằng nó, trên thực tế, có thể bị lỗi nếu tham số bool là một giá trị chưa được khởi tạo?

// Zero-filled global buffer of 16 characters
char destBuffer[16];

void Serialize(bool boolValue) {
    // Determine which string to print based on boolValue
    const char* whichString = boolValue ? "true" : "false";

    // Compute the length of the string we selected
    const size_t len = strlen(whichString);

    // Copy string into destination buffer, which is zero-filled (thus already null-terminated)
    memcpy(destBuffer, whichString, len);
}

Nếu mã này được thực thi với tối ưu hóa clang 5.0.0 +, nó sẽ / có thể bị lỗi.

Toán tử bậc ba được mong đợi boolValue ? "true" : "false"trông đủ an toàn đối với tôi, tôi đã giả định rằng, "Bất kể giá trị rác nào nằm trong boolValueđó đều không quan trọng, vì dù sao nó cũng sẽ đánh giá thành true hoặc false."

Tôi đã thiết lập một ví dụ Trình khám phá trình biên dịch hiển thị sự cố trong quá trình tháo gỡ, đây là ví dụ hoàn chỉnh. Lưu ý: để khắc phục sự cố, sự kết hợp mà tôi thấy đã hoạt động là sử dụng Clang 5.0.0 với tối ưu hóa -O2.

#include <iostream>
#include <cstring>

// Simple struct, with an empty constructor that doesn't initialize anything
struct FStruct {
    bool uninitializedBool;

   __attribute__ ((noinline))  // Note: the constructor must be declared noinline to trigger the problem
   FStruct() {};
};

char destBuffer[16];

// Small utility function that allocates and returns a string "true" or "false" depending on the value of the parameter
void Serialize(bool boolValue) {
    // Determine which string to print depending if 'boolValue' is evaluated as true or false
    const char* whichString = boolValue ? "true" : "false";

    // Compute the length of the string we selected
    size_t len = strlen(whichString);

    memcpy(destBuffer, whichString, len);
}

int main()
{
    // Locally construct an instance of our struct here on the stack. The bool member uninitializedBool is uninitialized.
    FStruct structInstance;

    // Output "true" or "false" to stdout
    Serialize(structInstance.uninitializedBool);
    return 0;
}

Vấn đề nảy sinh do trình tối ưu hóa: Nó đủ thông minh để suy ra rằng các chuỗi "true" và "false" chỉ khác nhau về độ dài 1. Vì vậy, thay vì thực sự tính toán độ dài, nó sử dụng giá trị của chính bool, điều này sẽ về mặt kỹ thuật là 0 hoặc 1 và như sau:

const size_t len = strlen(whichString); // original code
const size_t len = 5 - boolValue;       // clang clever optimization

Mặc dù điều này là "thông minh", do đó, câu hỏi của tôi là: Liệu tiêu chuẩn C ++ có cho phép trình biên dịch giả sử bool chỉ có thể có biểu diễn số bên trong là '0' hoặc '1' và sử dụng nó theo cách như vậy không?

Hay đây là trường hợp triển khai được xác định, trong trường hợp này, việc triển khai giả định rằng tất cả các bools của nó sẽ chỉ chứa 0 hoặc 1 và bất kỳ giá trị nào khác là lãnh thổ hành vi không xác định?

5 answers

292
Peter Cordes 2019-01-10 23:42.

Có, ISO C ++ cho phép (nhưng không yêu cầu) triển khai thực hiện lựa chọn này.

Nhưng cũng lưu ý rằng ISO C ++ cho phép trình biên dịch phát ra mã bị lỗi có chủ đích (ví dụ: với một lệnh bất hợp pháp) nếu chương trình gặp phải UB, ví dụ như một cách để giúp bạn tìm lỗi. (Hoặc vì đó là DeathStation 9000. Tuân thủ nghiêm ngặt là không đủ để triển khai C ++ trở nên hữu ích cho bất kỳ mục đích thực tế nào). Vì vậy, ISO C ++ sẽ cho phép trình biên dịch tạo asm bị lỗi (vì những lý do hoàn toàn khác nhau) ngay cả trên mã tương tự đọc một mã chưa được khởi tạo uint32_t. Mặc dù đó bắt buộc phải là loại bố cục cố định không có biểu diễn bẫy.

Đó là một câu hỏi thú vị về cách triển khai thực sự hoạt động, nhưng hãy nhớ rằng ngay cả khi câu trả lời khác, mã của bạn sẽ vẫn không an toàn vì C ++ hiện đại không phải là phiên bản di động của hợp ngữ.


Bạn đang biên dịch cho x86-64 System V ABI , chỉ định rằng a boolnhư một hàm đối số trong thanh ghi được biểu diễn bằng các mẫu bit false=0true=1 trong 8 bit thấp của thanh ghi 1 . Trong bộ nhớ, boollà loại 1 byte mà lại phải có giá trị nguyên là 0 hoặc 1.

(ABI là một tập hợp các lựa chọn triển khai mà các trình biên dịch cho cùng một nền tảng đồng ý để họ có thể tạo mã gọi các chức năng của nhau, bao gồm kích thước kiểu, quy tắc bố cục cấu trúc và quy ước gọi.)

ISO C ++ không chỉ định nó, nhưng quyết định ABI này phổ biến vì nó làm cho chuyển đổi bool-> int rẻ (chỉ là phần mở rộng bằng không) . Tôi không biết về bất kỳ ABI nào không cho phép trình biên dịch giả định là 0 hoặc 1 đối boolvới bất kỳ kiến ​​trúc nào (không chỉ x86). Nó cho phép tối ưu hóa như !myboolvới xor eax,1để lật bit thấp: Bất kỳ mã nào có thể có thể lật một bit / số nguyên / bool từ 0 đến 1 trong lệnh CPU đơn . Hoặc biên dịch a&&bsang bitwise AND cho boolcác loại. Một số trình biên dịch thực sự tận dụng các giá trị Boolean dưới dạng 8 bit trong trình biên dịch. Các hoạt động trên chúng không hiệu quả? .

Nói chung, quy tắc as-if cho phép trình biên dịch tận dụng lợi thế của những thứ đúng trên nền tảng đích đang được biên dịch , vì kết quả cuối cùng sẽ là mã thực thi thực hiện cùng một hành vi hiển thị bên ngoài như nguồn C ++. (Với tất cả các hạn chế mà Hành vi không xác định đặt ra đối với những gì thực sự "có thể nhìn thấy bên ngoài": không phải với trình gỡ lỗi, mà từ một luồng khác trong một chương trình C ++ hợp pháp / được định hình tốt.)

Trình biên dịch chắc chắn được phép tận dụng tối đa một bảo lãnh ABI trong nó mã gen, và làm cho mã như bạn thấy đó tối ưu hóa strlen(whichString)để
5U - boolValue.
(BTW, cách tối ưu hóa này khá thông minh, nhưng có thể thiển cận so với phân nhánh và nội tuyến memcpydưới dạng kho lưu trữ dữ liệu tức thì 2. )

Hoặc trình biên dịch có thể đã tạo một bảng con trỏ và lập chỉ mục nó với giá trị số nguyên của bool, một lần nữa giả sử nó là 0 hoặc 1. ( Chuẩn C ++ có cho phép bool chưa khởi tạo làm hỏng chương trình không? .)


Hàm __attribute((noinline))tạo của bạn với tối ưu hóa được kích hoạt dẫn đến việc chỉ cần tải một byte từ ngăn xếp để sử dụng uninitializedBool. Nó tạo không gian cho đối tượng trong mainvới push rax(nhỏ hơn và vì nhiều lý do khác nhau về hiệu quả như vậy sub rsp, 8), vì vậy bất cứ thứ gì rác trong AL khi nhập mainđều là giá trị mà nó được sử dụng uninitializedBool. Đây là lý do tại sao bạn thực sự nhận được những giá trị không chỉ 0.

5U - random garbagecó thể dễ dàng bọc thành một giá trị không được đánh dấu lớn, dẫn đến memcpy đi vào bộ nhớ chưa được ánh xạ. Đích đến nằm trong bộ nhớ tĩnh, không phải ngăn xếp, vì vậy bạn sẽ không ghi đè địa chỉ trả hàng hoặc thứ gì đó.


Các triển khai khác có thể tạo ra các lựa chọn khác nhau, ví dụ false=0true=any non-zero value. Sau đó, clang có thể sẽ không tạo ra mã bị lỗi cho phiên bản UB cụ thể này . (Nhưng nó vẫn được phép nếu nó muốn.) Tôi không biết bất kỳ triển khai nào chọn bất kỳ điều gì khác với những gì x86-64 làm bool, nhưng tiêu chuẩn C ++ cho phép nhiều thứ mà không ai làm hoặc thậm chí muốn làm. phần cứng giống như các CPU hiện tại.

ISO C ++ để lại nó không xác định những gì bạn sẽ tìm thấy khi bạn kiểm tra hoặc sửa đổi biểu diễn đối tượng của abool . (ví dụ bởi memcpying boolvào unsigned char, mà bạn được phép làm vì char*lon bí danh bất cứ điều gì. Và unsigned charđược đảm bảo để không có bit đệm, vì vậy chuẩn C ++ không chính thức cho phép bạn hexdump đại diện đối tượng mà không cần bất kỳ UB. Pointer-casting để sao chép các đối tượng Tất nhiên, biểu diễn khác với việc gán char foo = my_bool, vì vậy booleanization thành 0 hoặc 1 sẽ không xảy ra và bạn sẽ có được biểu diễn đối tượng thô.)

Bạn đã một phần "ẩn" các UB trên con đường thực hiện điều này từ trình biên dịch vớinoinline . Tuy nhiên, ngay cả khi nó không nội tuyến, tối ưu hóa liên thủ tục vẫn có thể tạo ra một phiên bản của hàm phụ thuộc vào định nghĩa của một hàm khác. (Thứ nhất, clang đang tạo ra một thư viện thực thi, không phải là một thư viện chia sẻ Unix nơi mà sự xen kẽ biểu tượng có thể xảy ra. Thứ hai, định nghĩa bên trong class{}định nghĩa nên tất cả các đơn vị dịch phải có cùng định nghĩa. Giống như với inlinetừ khóa.)

Vì vậy, một trình biên dịch có thể chỉ phát ra một rethoặc ud2(lệnh bất hợp pháp) làm định nghĩa main, bởi vì đường dẫn thực thi bắt đầu từ đầu của các mainhành vi không xác định không thể tránh khỏi. (Trình biên dịch có thể nhìn thấy tại thời điểm biên dịch nếu nó quyết định đi theo đường dẫn thông qua hàm tạo không nội tuyến.)

Bất kỳ chương trình nào gặp UB hoàn toàn không được xác định cho toàn bộ sự tồn tại của nó. Nhưng UB bên trong một hàm hoặc if()nhánh không bao giờ thực sự chạy không làm hỏng phần còn lại của chương trình. Trong thực tế, điều đó có nghĩa là trình biên dịch có thể quyết định phát ra một lệnh bất hợp pháp, hoặc a ret, hoặc không phát ra bất kỳ thứ gì và rơi vào khối / chức năng tiếp theo, cho toàn bộ khối cơ bản có thể được chứng minh tại thời điểm biên dịch để chứa hoặc dẫn đến UB.

GCC và Clang trong thực tế làm thực sự đôi khi phát ra ud2trên UB, thay vì thậm chí cố gắng để tạo mã cho đường dẫn thực hiện mà làm cho không có ý nghĩa. Hoặc đối với các trường hợp như rơi ra khỏi phần cuối của một voidhàm không phải là hàm, gcc đôi khi sẽ bỏ qua một retlệnh. Nếu bạn đang nghĩ rằng "hàm của tôi sẽ chỉ trả về với bất cứ thứ gì có trong RAX", bạn đã nhầm to. Các trình biên dịch C ++ hiện đại không coi ngôn ngữ này giống như một ngôn ngữ hợp ngữ di động nữa. Chương trình của bạn thực sự phải là C ++ hợp lệ, không đưa ra giả định về cách một phiên bản độc lập không nội tuyến của hàm của bạn có thể trông như thế nào.

Một ví dụ thú vị khác là Tại sao quyền truy cập trái dấu vào bộ nhớ mmap'ed đôi khi lại mặc định trên AMD64?. x86 không bị lỗi trên số nguyên không dấu, phải không? Vậy tại sao uint16_t*bị lệch lại là một vấn đề? Bởi vì alignof(uint16_t) == 2và vi phạm giả định đó đã dẫn đến lỗi mặc định khi tự động tạo vectơ với SSE2.

Xem thêm Những điều mà mọi lập trình viên C nên biết về Hành vi không xác định # 1/3 , một bài báo của một nhà phát triển clang.

Điểm chính: nếu trình biên dịch nhận thấy UB tại thời điểm biên dịch, nó có thể "phá vỡ" (phát ra asm đáng ngạc nhiên) đường dẫn thông qua mã của bạn gây ra UB ngay cả khi nhắm mục tiêu ABI nơi bất kỳ mẫu bit nào là đại diện đối tượng hợp lệ bool.

Mong đợi sự thù địch hoàn toàn đối với nhiều sai lầm của lập trình viên, đặc biệt là những điều mà các trình biên dịch hiện đại cảnh báo. Đây là lý do tại sao bạn nên sử dụng -Wallvà sửa chữa các cảnh báo. C ++ không phải là một ngôn ngữ thân thiện với người dùng và một thứ gì đó trong C ++ có thể không an toàn ngay cả khi nó sẽ an toàn trong asm đối với mục tiêu mà bạn đang biên dịch. (ví dụ: tràn đã ký là UB trong C ++ và trình biên dịch sẽ cho rằng điều đó không xảy ra, ngay cả khi biên dịch cho phần bổ sung của 2 x86, trừ khi bạn sử dụng clang/gcc -fwrapv.)

UB hiển thị theo thời gian biên dịch luôn nguy hiểm và thực sự khó để chắc chắn (với tối ưu hóa thời gian liên kết) rằng bạn đã thực sự ẩn UB khỏi trình biên dịch và do đó có thể suy luận về loại asm mà nó sẽ tạo ra.

Không quá kịch tính; thường các trình biên dịch cho phép bạn xử lý một số thứ và phát ra mã như bạn đang mong đợi ngay cả khi một thứ gì đó là UB. Nhưng có thể sẽ là một vấn đề trong tương lai nếu các nhà phát triển trình biên dịch thực hiện một số tối ưu hóa để thu được nhiều thông tin hơn về các phạm vi giá trị (ví dụ: một biến không âm, có thể cho phép nó tối ưu hóa phần mở rộng ký thành phần mở rộng 0 miễn phí trên x86- 64). Ví dụ: trong gcc và clang hiện tại, doing tmp = a+INT_MINkhông tối ưu hóa a<0là always-false, chỉ có điều đó tmpluôn là tiêu cực. (Bởi vì INT_MIN+ a=INT_MAXlà âm đối với mục tiêu bổ sung của 2 thứ này và akhông thể cao hơn mức đó.)

Vì vậy, gcc / clang hiện không quay trở lại để lấy thông tin phạm vi cho các đầu vào của phép tính, chỉ dựa trên kết quả dựa trên giả định không có tràn có dấu: ví dụ trên Godbolt . Tôi không biết liệu đây có phải là sự tối ưu hóa được cố tình "bỏ qua" dưới danh nghĩa thân thiện với người dùng hay không.

Cũng lưu ý rằng các triển khai (hay còn gọi là trình biên dịch) được phép xác định hành vi mà ISO C ++ để lại là không xác định . Ví dụ: tất cả các trình biên dịch hỗ trợ bản chất của Intel (như _mm_add_ps(__m128, __m128)đối với vectơ hóa SIMD thủ công) phải cho phép tạo các con trỏ căn chỉnh sai, là UB trong C ++ ngay cả khi bạn không tham khảo chúng. __m128i _mm_loadu_si128(const __m128i *)không cân bằng tải bằng cách lấy một đối số sai lệch __m128i*, không phải a void*hoặc char*. Việc `reinterpret_cast`ing giữa con trỏ vectơ SIMD phần cứng và kiểu tương ứng có phải là hành vi không xác định không?

GNU C / C ++ cũng định nghĩa hành vi dịch sang trái một số có dấu âm (thậm chí không có -fwrapv), tách biệt với các quy tắc UB tràn có dấu thông thường. ( Tại sao hoạt động dịch trái gọi Hành vi không xác định khi toán hạng bên trái có giá trị âm? , trong khi các dịch chuyển bên phải của các số có dấu được xác định bởi việc triển khai (logic so với số học); triển khai chất lượng tốt chọn số học trên HW có dịch chuyển bên phải số học, nhưng ISO C ++ không chỉ định). Điều này được ghi lại trong phần Số nguyên của sổ tay GCC , cùng với việc xác định hành vi do triển khai xác định mà các tiêu chuẩn C yêu cầu triển khai để xác định theo cách này hay cách khác.

Chắc chắn có những vấn đề về chất lượng triển khai mà các nhà phát triển trình biên dịch quan tâm; Nhìn chung, họ không cố gắng tạo ra các trình biên dịch có chủ đích thù địch, nhưng việc tận dụng tất cả các ổ gà UB trong C ++ (ngoại trừ những ổ gà mà họ chọn để xác định) để tối ưu hóa tốt hơn đôi khi gần như không thể phân biệt được.


Chú thích 1 : 56 bit phía trên có thể là rác mà bộ nhớ phải bỏ qua, như thường lệ đối với các loại hẹp hơn một thanh ghi.

( Các ABI khác thực hiện các lựa chọn khác nhau ở đây . Một số yêu cầu các kiểu số nguyên hẹp là không hoặc có dấu mở rộng để điền vào một thanh ghi khi được chuyển đến hoặc trả về từ các hàm, như MIPS64 và PowerPC64. Xem phần cuối cùng của MOVZX thiếu thanh ghi 32 bit thành thanh ghi 64 bit .)

Ví dụ: một người gọi có thể đã tính toán a & 0x01010101trong RDI và sử dụng nó cho việc khác, trước khi gọi bool_func(a&1). Người gọi có thể tối ưu hóa loại bỏ &1bởi vì nó đã làm điều đó với byte thấp như một phần của and edi, 0x01010101, và nó biết callee được yêu cầu bỏ qua các byte cao.

Hoặc nếu bool được chuyển như đối số thứ 3, có thể người gọi tối ưu hóa kích thước mã tải nó mov dl, [mem]thay vì movzx edx, [mem], tiết kiệm 1 byte với chi phí phụ thuộc sai vào giá trị cũ của RDX (hoặc hiệu ứng thanh ghi từng phần khác, tùy thuộc trên mô hình CPU). Hoặc đối với đối số đầu tiên, mov dil, byte [r10]thay vì movzx edi, byte [r10], vì cả hai đều yêu cầu tiền tố REX.

Đây là lý do tại sao tiếng kêu phát ra movzx eax, diltrong Serialize, thay vì sub eax, edi. (Đối với args số nguyên, clang vi phạm quy tắc ABI này, thay vào đó tùy thuộc vào hành vi không có tài liệu của gcc và clang đối với số nguyên hẹp Có cần mở rộng dấu hoặc không khi thêm độ lệch 32 bit vào con trỏ cho ABI x86-64 không? hoặc dấu-mở rộng đến 32 bit. Có cần mở rộng dấu hoặc không khi thêm độ lệch 32 bit vào con trỏ cho ABI x86-64 không? Vì vậy, tôi quan tâm đến việc nó không làm điều tương tự đối với bool.)


Chú thích 2: Sau khi phân nhánh, bạn sẽ chỉ có một lưu trữ 4 byte mov-im ngay lập tức hoặc 4 byte + 1 byte. Chiều dài ẩn trong chiều rộng cửa hàng + hiệu số.

OTOH, glibc memcpy sẽ thực hiện hai lần tải / lưu trữ 4 byte với sự chồng chéo phụ thuộc vào độ dài, vì vậy điều này thực sự làm cho toàn bộ mọi thứ không có các nhánh có điều kiện trên boolean. Xem L(between_4_7):khối trong memcpy / memmove của glibc. Hoặc ít nhất, hãy làm theo cách tương tự cho boolean trong phân nhánh của memcpy để chọn kích thước phân đoạn.

Nếu nội tuyến, bạn có thể sử dụng 2x mov-im ngay + cmovvà bù đắp có điều kiện hoặc bạn có thể để dữ liệu chuỗi trong bộ nhớ.

Hoặc nếu điều chỉnh cho Intel Ice Lake ( với tính năng REP MOV nhanh ngắn ), thực tế rep movsbcó thể là tối ưu. glibc memcpycó thể bắt đầu sử dụng rep movsb cho các kích thước nhỏ trên các CPU có tính năng đó, tiết kiệm rất nhiều phân nhánh.


Các công cụ để phát hiện UB và việc sử dụng các giá trị chưa được khởi tạo

Trong gcc và clang, bạn có thể biên dịch với -fsanitize=undefinedđể thêm thiết bị đo thời gian chạy sẽ cảnh báo hoặc lỗi trên UB xảy ra trong thời gian chạy. Tuy nhiên, điều đó sẽ không bắt các biến đơn nguyên. (Vì nó không tăng kích thước kiểu để nhường chỗ cho một bit "chưa được khởi tạo").

Xem https://developers.redhat.com/blog/2014/10/16/gcc-undefined-behavior-sanitizer-ubsan/

Để tìm cách sử dụng dữ liệu chưa được khởi tạo, có Trình vệ sinh địa chỉ và Trình vệ sinh bộ nhớ trong clang / LLVM. https://github.com/google/sanitizers/wiki/MemorySanitizer hiển thị các ví dụ về cách clang -fsanitize=memory -fPIE -piephát hiện các lần đọc bộ nhớ chưa được khởi tạo. Nó có thể hoạt động tốt nhất nếu bạn biên dịch mà không tối ưu hóa, vì vậy tất cả các lần đọc các biến cuối cùng thực sự tải từ bộ nhớ trong asm. Họ cho thấy nó đang được sử dụng -O2trong trường hợp tải không tối ưu hóa. Tôi đã không thử nó bản thân mình. (Trong một số trường hợp, chẳng hạn như không khởi tạo bộ tích lũy trước khi tính tổng một mảng, clang -O3 sẽ phát ra mã tổng hợp vào một thanh ghi vectơ mà nó chưa bao giờ được khởi tạo. Vì vậy, với tối ưu hóa, bạn có thể gặp trường hợp không có bộ nhớ đọc được liên kết với UB . Nhưng -fsanitize=memorythay đổi asm đã tạo và có thể dẫn đến việc kiểm tra điều này.)

Nó sẽ chấp nhận việc sao chép bộ nhớ chưa được khởi tạo, cũng như các phép toán logic và số học đơn giản với nó. Nói chung, MemorySanitizer âm thầm theo dõi sự lây lan của dữ liệu chưa được khởi tạo trong bộ nhớ và báo cáo cảnh báo khi một nhánh mã được lấy (hoặc không được lấy) tùy thuộc vào giá trị chưa được khởi tạo.

MemorySanitizer triển khai một tập hợp con các chức năng có trong Valgrind (công cụ Memcheck).

Nó sẽ hoạt động cho trường hợp này vì lệnh gọi đến glibc memcpyvới một lengthtính toán từ bộ nhớ chưa khởi tạo sẽ (bên trong thư viện) dẫn đến một nhánh dựa trên length. Nếu nó có nội dung một phiên bản hoàn toàn không có nhánh chỉ sử dụng cmov, lập chỉ mục và hai cửa hàng, nó có thể không hoạt động.

Valgrind'smemcheck cũng sẽ tìm kiếm loại vấn đề này, một lần nữa sẽ không phàn nàn nếu chương trình chỉ sao chép xung quanh dữ liệu chưa được khởi tạo. Nhưng nó cho biết nó sẽ phát hiện khi nào "Bước nhảy hoặc di chuyển có điều kiện phụ thuộc vào (các) giá trị chưa được khởi tạo", để cố gắng bắt bất kỳ hành vi nào có thể nhìn thấy bên ngoài phụ thuộc vào dữ liệu chưa được khởi tạo.

Có lẽ ý tưởng đằng sau việc không gắn cờ chỉ một tải là cấu trúc có thể có phần đệm và việc sao chép toàn bộ cấu trúc (bao gồm cả phần đệm) với tải / lưu trữ vectơ rộng không phải là một lỗi ngay cả khi các thành viên riêng lẻ chỉ được viết một lần. Ở cấp độ asm, thông tin về những gì đã được đệm và những gì thực sự là một phần của giá trị đã bị mất.

56
rici 2019-01-10 15:59.

Trình biên dịch được phép giả định rằng một giá trị boolean được truyền làm đối số là một giá trị boolean hợp lệ (tức là một giá trị đã được khởi tạo hoặc chuyển đổi thành truehoặc false). Các truegiá trị không phải là giống như các số nguyên 1 - thực sự, có thể có cơ quan đại diện khác nhau của truefalse- nhưng các tham số phải có một số đại diện hợp lệ của một trong hai giá trị, trong đó "đại diện hợp lệ" là implementation- được xác định.

Vì vậy, nếu bạn không khởi tạo được a boolhoặc nếu bạn thành công trong việc ghi đè nó thông qua một số con trỏ thuộc loại khác, thì các giả định của trình biên dịch sẽ sai và Hành vi không xác định sẽ xảy ra sau đó. Bạn đã được cảnh báo:

50) Sử dụng giá trị bool theo những cách được tiêu chuẩn này mô tả là “không xác định”, chẳng hạn như bằng cách kiểm tra giá trị của một đối tượng tự động chưa được khởi tạo, có thể khiến nó hoạt động như thể nó không đúng cũng không sai. (Chú thích đoạn 6 của §6.9.1, Các loại cơ bản)

52
M.M 2019-01-10 16:12.

Bản thân hàm đúng, nhưng trong chương trình thử nghiệm của bạn, câu lệnh gọi hàm gây ra hành vi không xác định bằng cách sử dụng giá trị của một biến chưa được khởi tạo.

Lỗi nằm trong chức năng gọi và nó có thể được phát hiện bằng cách xem xét mã hoặc phân tích tĩnh của chức năng gọi. Sử dụng liên kết trình khám phá trình biên dịch của bạn, trình biên dịch gcc 8.2 sẽ phát hiện ra lỗi. (Có thể bạn có thể gửi một báo cáo lỗi chống lại tiếng kêu rằng nó không tìm thấy sự cố).

Hành vi không xác định có nghĩa là bất kỳ điều gì có thể xảy ra, bao gồm việc chương trình bị lỗi một vài dòng sau sự kiện kích hoạt hành vi không xác định.

NB. Câu trả lời cho "Hành vi không xác định có thể gây ra _____ không?" luôn luôn là "Có". Đó thực sự là định nghĩa của hành vi không xác định.

23
Barmar 2019-01-10 16:02.

Một bool chỉ được phép lưu giữ các giá trị phụ thuộc vào việc triển khai được sử dụng nội bộ cho truefalsevà mã được tạo có thể giả định rằng nó sẽ chỉ giữ một trong hai giá trị này.

Thông thường, việc triển khai sẽ sử dụng số nguyên 0for false1for true, để đơn giản hóa các chuyển đổi giữa boolintif (boolvar)tạo cùng một mã như if (intvar). Trong trường hợp đó, người ta có thể tưởng tượng rằng mã được tạo cho chuỗi thứ ba trong phép gán sẽ sử dụng giá trị làm chỉ mục thành một mảng con trỏ đến hai chuỗi, tức là nó có thể được chuyển đổi thành một cái gì đó như:

// the compile could make asm that "looks" like this, from your source
const static char *strings[] = {"false", "true"};
const char *whichString = strings[boolValue];

Nếu boolValuechưa được khởi tạo, nó thực sự có thể chứa bất kỳ giá trị số nguyên nào, sau đó sẽ gây ra việc truy cập bên ngoài giới hạn của stringsmảng.

15
Tom Tanner 2019-01-11 01:48.

Tóm lại câu hỏi của bạn rất nhiều, bạn đang hỏi Liệu tiêu chuẩn C ++ có cho phép trình biên dịch giả sử a boolchỉ có thể có một biểu diễn số bên trong là '0' hoặc '1' và sử dụng nó theo cách như vậy không?

Tiêu chuẩn không nói gì về đại diện bên trong của a bool. Nó chỉ xác định điều gì sẽ xảy ra khi truyền một boolthành một int(hoặc ngược lại). Hầu hết, do các chuyển đổi tích hợp này (và thực tế là mọi người phụ thuộc khá nhiều vào chúng), trình biên dịch sẽ sử dụng 0 và 1, nhưng nó không bắt buộc (mặc dù nó phải tôn trọng các ràng buộc của bất kỳ ABI cấp thấp hơn nào mà nó sử dụng ).

Vì vậy, trình biên dịch, khi nó nhìn thấy a boolđược quyền xem xét điều đó đã nói boolchứa một trong hai mẫu bit ' true' hoặc ' false' và làm bất cứ điều gì mà nó cảm thấy thích. Vì vậy, nếu các giá trị cho truefalseđược 1 và 0, tương ứng, trình biên dịch thực sự là phép tối ưu hóa strlenđể 5 - <boolean value>. Các hành vi vui nhộn khác là có thể!

Như đã được nêu nhiều lần ở đây, hành vi không xác định có kết quả không xác định. Bao gồm nhưng không giới hạn trong

  • Mã của bạn hoạt động như bạn mong đợi
  • Mã của bạn không thành công vào những thời điểm ngẫu nhiên
  • Mã của bạn không được chạy chút nào.

Xem Những gì mọi lập trình viên nên biết về hành vi không xác định

Related questions

MORE COOL STUFF

Cate Blanchett chia tay chồng sau 3 ngày bên nhau và vẫn kết hôn với anh ấy 25 năm sau

Cate Blanchett chia tay chồng sau 3 ngày bên nhau và vẫn kết hôn với anh ấy 25 năm sau

Cate Blanchett đã bất chấp những lời khuyên hẹn hò điển hình khi cô gặp chồng mình.

Tại sao Michael Sheen là một diễn viên phi lợi nhuận

Tại sao Michael Sheen là một diễn viên phi lợi nhuận

Michael Sheen là một diễn viên phi lợi nhuận nhưng chính xác thì điều đó có nghĩa là gì?

Hallmark Star Colin Egglesfield Các món ăn gây xúc động mạnh đối với người hâm mộ tại RomaDrama Live! [Loại trừ]

Hallmark Star Colin Egglesfield Các món ăn gây xúc động mạnh đối với người hâm mộ tại RomaDrama Live! [Loại trừ]

Ngôi sao của Hallmark Colin Egglesfield chia sẻ về những cuộc gặp gỡ với người hâm mộ ly kỳ tại RomaDrama Live! cộng với chương trình INSPIRE của anh ấy tại đại hội.

Tại sao bạn không thể phát trực tuyến 'chương trình truyền hình phía Bắc'

Tại sao bạn không thể phát trực tuyến 'chương trình truyền hình phía Bắc'

Bạn sẽ phải phủi sạch đầu đĩa Blu-ray hoặc DVD để xem tại sao Northern Exposure trở thành một trong những chương trình nổi tiếng nhất của thập niên 90.

Where in the World Are You? Take our GeoGuesser Quiz

Where in the World Are You? Take our GeoGuesser Quiz

The world is a huge place, yet some GeoGuessr players know locations in mere seconds. Are you one of GeoGuessr's gifted elite? Take our quiz to find out!

8 công dụng tuyệt vời của Baking Soda và Giấm

8 công dụng tuyệt vời của Baking Soda và Giấm

Bạn biết đấy, hai sản phẩm này là nguồn điện để làm sạch, riêng chúng. Nhưng cùng với nhau, chúng có một loạt công dụng hoàn toàn khác.

Hạn hán, biến đổi khí hậu đe dọa tương lai của thủy điện Hoa Kỳ

Hạn hán, biến đổi khí hậu đe dọa tương lai của thủy điện Hoa Kỳ

Thủy điện rất cần thiết cho lưới điện của Hoa Kỳ, nhưng nó chỉ tạo ra năng lượng khi có nước di chuyển. Bao nhiêu nhà máy thủy điện có thể gặp nguy hiểm khi các hồ và sông cạn kiệt?

Quyên góp tóc của bạn để giúp giữ nước sạch của chúng tôi

Quyên góp tóc của bạn để giúp giữ nước sạch của chúng tôi

Tóc tỉa từ các tiệm và các khoản quyên góp cá nhân có thể được tái sử dụng như những tấm thảm thấm dầu và giúp bảo vệ môi trường.

Tất nhiên Luật sư mới của Harvey Weinstein là một người phụ nữ chỉ trích tôi

Tất nhiên Luật sư mới của Harvey Weinstein là một người phụ nữ chỉ trích tôi

Phiên tòa xét xử vụ tấn công tình dục bị trì hoãn nhiều của Harvey Weinstein dự kiến ​​sẽ bắt đầu lựa chọn bồi thẩm đoàn vào tháng 9, và với dự đoán, ông đã sắp xếp lại đội ngũ pháp lý của mình lần thứ ba. Có lẽ không thể tránh khỏi, các luật sư của anh ấy hiện bao gồm luật sư bào chữa Donna Rotunno, người nổi tiếng với việc đại diện cho những người đàn ông bị cáo buộc tấn công tình dục, đặt câu hỏi về phong trào MeToo nói chung và đã nói trong các cuộc phỏng vấn trước đây rằng giới tính của cô ấy cho phép cô ấy thoát khỏi "nọc độc" nhiều hơn khi kiểm tra chéo người tố cáo.

Cựu trợ lý của Harvey Weinstein nói rằng ông là 'kẻ thao túng bậc thầy'

Cựu trợ lý của Harvey Weinstein nói rằng ông là 'kẻ thao túng bậc thầy'

Trong một cuộc phỏng vấn với BBC News, trợ lý cũ của Harvey Weinstein, Zelda Perkins, nói rõ thực tế của việc chống lại ông trùm Hollywood. Cô ấy nói, về cơ bản là không thể.

Không có người chơi nào trên bầu trời của họ có cuộc chiến đầu tiên của họ và nó đã giết chết hoàn toàn một nền văn minh

Không có người chơi nào trên bầu trời của họ có cuộc chiến đầu tiên của họ và nó đã giết chết hoàn toàn một nền văn minh

Nguồn ảnh: AndyKrycek6 Những người hâm mộ No Man's Sky không thể chơi với nhau một cách chính xác, nhưng họ vẫn thích đến với nhau để ăn mừng cộng đồng của họ. Bằng cách nào đó, một kỳ nghỉ mới do người chơi tạo ra đã trở thành một câu chuyện về xung đột, phản bội và sự biến mất của cả một nền văn minh.

Cựu Giám đốc Thanh niên Bắc Carolina NAACP buộc tội Giám sát viên về hành vi quấy rối tình dục và cưỡng bức như thế nào Quốc gia xử lý yêu cầu của cô ấy

Cựu Giám đốc Thanh niên Bắc Carolina NAACP buộc tội Giám sát viên về hành vi quấy rối tình dục và cưỡng bức như thế nào Quốc gia xử lý yêu cầu của cô ấy

Jazmyne Childs đã khóc trong một cuộc họp báo vào ngày 25 tháng 9 năm 2019, khi cô ấy mô tả về hành vi quấy rối tình dục mà cô ấy nói rằng cô ấy đã phải chịu đựng khi làm việc cho phân hội Bắc Carolina của NAACP, ở Raleigh, N.

Nicky Hilton Forced to Borrow Paris' 'I Love Paris' Sweatshirt After 'Airline Loses All [My] Luggage'

Nicky Hilton Forced to Borrow Paris' 'I Love Paris' Sweatshirt After 'Airline Loses All [My] Luggage'

Nicky Hilton Rothschild's luggage got lost, but luckily she has an incredible closet to shop: Sister Paris Hilton's!

Kate Middleton dành một ngày bên bờ nước ở London, cùng với Jennifer Lopez, Julianne Hough và hơn thế nữa

Kate Middleton dành một ngày bên bờ nước ở London, cùng với Jennifer Lopez, Julianne Hough và hơn thế nữa

Kate Middleton dành một ngày bên bờ nước ở London, cùng với Jennifer Lopez, Julianne Hough và hơn thế nữa. Từ Hollywood đến New York và mọi nơi ở giữa, hãy xem các ngôi sao yêu thích của bạn đang làm gì!

17 tuổi bị đâm chết trong khi 4 người khác bị thương trong một cuộc tấn công bằng dao trên sông Wisconsin

17 tuổi bị đâm chết trong khi 4 người khác bị thương trong một cuộc tấn công bằng dao trên sông Wisconsin

Các nhà điều tra đang xem xét liệu nhóm và nghi phạm có biết nhau trước vụ tấn công hay không

Thanh thiếu niên, Gia đình Florida Hội đồng quản trị trường học về Luật 'Không nói đồng tính': 'Buộc chúng tôi tự kiểm duyệt'

Thanh thiếu niên, Gia đình Florida Hội đồng quản trị trường học về Luật 'Không nói đồng tính': 'Buộc chúng tôi tự kiểm duyệt'

Vụ kiện, nêu tên một số học khu, lập luận rằng dự luật "Không nói đồng tính" được ban hành gần đây của Florida "có hiệu quả im lặng và xóa bỏ học sinh và gia đình LGBTQ +"

Tôi viết như thế nào

Tôi viết như thế nào

Đối với tôi, mọi thứ là về dòng đầu tiên đó và nó sẽ đưa bạn đến đâu. Một số nhà văn bị điều khiển bởi cốt truyện, sự sắp xếp tinh tế của các quân cờ, trong khi những người khác bị lôi cuốn bởi một nhân vật và khả năng thực hiện một cuộc hành trình với một người bạn hư cấu mới.

Đường băng hạ cánh

Đường băng hạ cánh

Cuối hè đầu thu là mùa hoài niệm. Những chiếc đèn đường chiếu ánh sáng của chúng qua những con đường đẫm mưa, và những chiếc lá dưới chân - màu đỏ cam tắt trong bóng chạng vạng - là lời nhắc nhở về những ngày đã qua.

Hãy tưởng tượng tạo ra một chiến lược nội dung thực sự CHUYỂN ĐỔI. Nó có thể.

Hãy tưởng tượng tạo ra một chiến lược nội dung thực sự CHUYỂN ĐỔI. Nó có thể.

Vào năm 2021, tôi khuyến khích bạn suy nghĩ lại mọi thứ bạn biết về khách hàng mà bạn phục vụ và những câu chuyện bạn kể cho họ. Lùi lại.

Sự mất mát của voi ma mút đã mở ra trái tim tôi để yêu

Sự mất mát của voi ma mút đã mở ra trái tim tôi để yêu

Vào ngày sinh nhật thứ 9 của Felix The Cat, tôi nhớ về một trong những mất mát lớn nhất trong cuộc đời trưởng thành của tôi - Sophie của tôi vào năm 2013. Tôi đã viết bài luận này và chia sẻ nó trên nền tảng này một thời gian ngắn vào năm 2013.

Language