Câu hỏi Điều gì làm cho việc sử dụng con trỏ này không thể đoán trước?


Tôi hiện đang tìm hiểu con trỏ và giáo sư của tôi đã cung cấp đoạn mã này làm ví dụ:

//We cannot predict the behavior of this program!

#include <iostream>
using namespace std;

int main()
{
    char * s = "My String";
    char s2[] = {'a', 'b', 'c', '\0'};

    cout << s2 << endl;

    return 0;
}

Ông đã viết trong các ý kiến ​​rằng chúng tôi không thể dự đoán hành vi của chương trình. Điều gì chính xác làm cho nó không thể đoán trước mặc dù? Tôi thấy không có gì sai với nó.


108
2017-08-04 18:19


gốc


Bạn có chắc là bạn đã sao chép mã của giáo sư một cách chính xác không? Mặc dù chính thức có thể cho rằng chương trình này có thể tạo ra hành vi "không thể đoán trước", nhưng không có ý nghĩa gì cả. Và tôi nghi ngờ rằng bất kỳ giáo sư nào cũng sẽ sử dụng một cái gì đó bí truyền để minh họa cho "không thể đoán trước" cho sinh viên. - AnT
@Lightness Races trong Orbit: Trình biên dịch được phép "chấp nhận" mã hình thành không đúng sau khi phát hành các thông báo chẩn đoán bắt buộc. Nhưng đặc tả ngôn ngữ không xác định hành vi của mã. I E. vì lỗi khi khởi tạo s, chương trình, nếu được chấp nhận bởi một số trình biên dịch, chính thức có hành vi không thể đoán trước. - AnT
@ TheParamagneticCroissant: Không. Việc khởi tạo là hình thành không tốt trong thời hiện đại. - Lightness Races in Orbit
@The Croissant Paramagnetic: Như tôi đã nói ở trên, ngôn ngữ không yêu cầu mã hình thành không thành công để "không biên dịch". Trình biên dịch chỉ đơn giản là cần thiết để đưa ra một chẩn đoán. Sau đó, họ được phép tiếp tục và "biên dịch mã thành công". Tuy nhiên, hành vi của mã như vậy không được xác định bởi đặc tả ngôn ngữ. - AnT
Tôi rất muốn biết câu trả lời của giáo sư của bạn là gì. - Daniël W. Crompton


Các câu trả lời:


Hành vi của chương trình là không tồn tại, bởi vì nó được hình thành không đúng.

char* s = "My String";

Điều này là bất hợp pháp. Trước năm 2011, nó đã không được chấp nhận trong 12 năm.

Dòng chính xác là:

const char* s = "My String";

Ngoài ra, chương trình còn tốt. Giáo sư của bạn nên uống ít rượu whiskey!


124
2017-08-04 18:25



với -pantic nó: main.cpp: 6: 16: cảnh báo: ISO C ++ cấm chuyển đổi một hằng số chuỗi thành 'char *' [-Wpedantic] - marcinj
@ black: Không, thực tế là việc chuyển đổi là bất hợp pháp làm cho chương trình bị hình thành. Nó không được chấp nhận trong quá khứ. Chúng tôi không còn trong quá khứ. - Lightness Races in Orbit
(Đó là ngớ ngẩn vì đó là mục đích của việc không dùng nữa trong 12 năm) - Lightness Races in Orbit
@black: Hành vi của một chương trình bị ốm là không phải "được xác định hoàn toàn". - Lightness Races in Orbit
Bất kể, câu hỏi là về C + +, không phải về một số phiên bản cụ thể của GCC. - Lightness Races in Orbit


Câu trả lời là: nó phụ thuộc vào tiêu chuẩn C ++ mà bạn đang biên dịch. Tất cả các mã được tạo thành một cách hoàn hảo trên tất cả các tiêu chuẩn ‡ ngoại trừ dòng này:

char * s = "My String";

Bây giờ, chuỗi ký tự có loại const char[10] và chúng tôi đang cố gắng khởi tạo con trỏ không phải const. Đối với tất cả các loại khác ngoài char gia đình của chuỗi chữ, chẳng hạn một khởi tạo luôn luôn là bất hợp pháp. Ví dụ:

const int arr[] = {1};
int *p = arr; // nope!

Tuy nhiên, trong pre-C ++ 11, đối với chuỗi ký tự, có một ngoại lệ trong §4.2 / 2:

Một chuỗi ký tự (2.13.4) không phải là một chuỗi ký tự lớn có thể được chuyển đổi thành một giá trị của loại “con trỏ đến char”; [...]. Trong cả hai trường hợp, kết quả là một con trỏ đến phần tử đầu tiên của mảng. Chuyển đổi này chỉ được xem xét khi có loại mục tiêu con trỏ thích hợp rõ ràng và không phải khi có nhu cầu chung để chuyển đổi từ một giá trị thành giá trị. [Chú thích: chuyển đổi này là không dùng nữa. Xem Phụ lục D. ] 

Vì vậy, trong C ++ 03, mã là hoàn toàn tốt (mặc dù không được chấp nhận), và có hành vi rõ ràng, dự đoán được.

Trong C ++ 11, khối đó không tồn tại - không có ngoại lệ nào cho các chuỗi ký tự được chuyển đổi thành char*, và vì vậy mã chỉ là hình thành không đúng như int* ví dụ tôi vừa cung cấp. Trình biên dịch bắt buộc phải đưa ra một chẩn đoán, và lý tưởng trong các trường hợp như là vi phạm rõ ràng của hệ thống kiểu C ++, chúng ta mong đợi một trình biên dịch tốt không chỉ phù hợp với vấn đề này (ví dụ bằng cách đưa ra cảnh báo). ngay.

Mã này lý tưởng không biên dịch - nhưng trên cả gcc và clang (tôi giả sử vì có thể có rất nhiều mã ở đó sẽ bị phá vỡ với ít được, mặc dù lỗ hệ thống kiểu này không được dùng nữa trong hơn một thập kỷ). Mã này không đúng định dạng và do đó không có lý do gì về hành vi của mã. Nhưng xem xét trường hợp cụ thể này và lịch sử của nó đã được cho phép trước đó, tôi không tin đó là một đoạn không hợp lý để diễn giải mã kết quả như thể nó là một ẩn const_cast, cái gì đó như:

const int arr[] = {1};
int *p = const_cast<int*>(arr); // OK, technically

Cùng với đó, phần còn lại của chương trình hoàn toàn ổn, vì bạn chưa bao giờ thực sự chạm vào s lần nữa. đọc hiểu một tạo ra-const đối tượng thông qua một khôngconst con trỏ là hoàn toàn OK. Viết một tạo ra-const đối tượng thông qua con trỏ như vậy là hành vi không xác định:

std::cout << *p; // fine, prints 1
*p = 5;          // will compile, but undefined behavior, which
                 // certainly qualifies as "unpredictable"

Vì không có sửa đổi qua s bất cứ nơi nào trong mã của bạn, chương trình là tốt trong C ++ 03, nên không biên dịch trong C ++ 11 nhưng không anyway - và cho rằng các trình biên dịch cho phép nó, vẫn không có hành vi không xác định trong nó †. Với các khoản phụ cấp mà các trình biên dịch vẫn còn [không chính xác] diễn giải các quy tắc C ++ 03, tôi không thấy gì có thể dẫn đến hành vi "không thể đoán trước". Viết thư cho s mặc dù, và tất cả mọi phiên cược đều bị tắt. Trong cả C ++ 03 và C ++ 11.


† Mặc dù, một lần nữa, theo định nghĩa mã hình thành không có hiệu quả, không có kỳ vọng về hành vi hợp lý
‡ Ngoại trừ không, xem Câu trả lời của Matt McNabb


81
2017-08-04 18:50



Tôi nghĩ ở đây "không thể đoán trước" được dự định bởi giáo sư có nghĩa là người ta không thể sử dụng tiêu chuẩn để dự đoán trình biên dịch sẽ làm gì với mã hình thành không đúng (ngoài việc đưa ra chẩn đoán). Có, nó có thể xử lý nó như C ++ 03 nói rằng nó nên được xử lý, và (có nguy cơ "Không True Scotsman" fallacy) thông thường cho phép chúng tôi dự đoán với một số sự tự tin rằng đây là điều duy nhất một trình biên dịch hợp lý-nhà văn sẽ bao giờ chọn nếu mã biên dịch ở tất cả. Sau đó, một lần nữa, nó có thể xử lý nó như là ý nghĩa để đảo ngược chuỗi chữ trước khi đúc nó để không const. Tiêu chuẩn C ++ không quan tâm. - Steve Jessop
@SteveJessop Tôi không mua giải thích đó. Đây không phải là hành vi không xác định cũng như của danh mục mã không đúng định dạng mà các nhãn chuẩn không cần chẩn đoán. Đó là một loại vi phạm hệ thống kiểu đơn giản nên rất dễ dự đoán (biên dịch và làm những việc bình thường trên C ++ 03, không biên dịch trên C ++ 11). Bạn không thể thực sự sử dụng các lỗi trình biên dịch (hoặc các giấy phép nghệ thuật) để gợi ý rằng mã không thể đoán trước được - nếu không thì tất cả các mã đều sẽ không thể đoán trước được. - Barry
Tôi không nói về lỗi trình biên dịch, tôi đang nói về việc có hay không tiêu chuẩn xác định hành vi (nếu có) của mã. Tôi nghi ngờ các giáo sư đang làm như vậy, và "không thể đoán trước" chỉ là một ham-fisted cách nói rằng các tiêu chuẩn hiện hành không xác định hành vi. Dù sao có vẻ như có nhiều khả năng với tôi, hơn là giáo sư không chính xác tin rằng đây là một chương trình được hình thành tốt với hành vi không xác định. - Steve Jessop
Không nó không. Tiêu chuẩn này không xác định hành vi của các chương trình không đúng định dạng. - Steve Jessop
@supercat: đó là một điểm công bằng, nhưng tôi không tin đó là lý do chính. Tôi nghĩ nguyên nhân chính là tiêu chuẩn không xác định hành vi của các chương trình không đúng định dạng, để các trình biên dịch có thể hỗ trợ các phần mở rộng cho ngôn ngữ bằng cách thêm cú pháp không được định dạng đúng (như Objective C). Cho phép thực hiện để làm cho một tổng số horlicks ra khỏi làm sạch sau khi một biên dịch không thành công chỉ là một tiền thưởng :-) - Steve Jessop


Các câu trả lời khác đã đề cập rằng chương trình này không đúng trong C ++ 11 do việc gán const char mảng đến một char *.

Tuy nhiên, chương trình này cũng không đúng với C ++ 11.

Các operator<< quá tải đang ở <ostream>. Yêu cầu cho iostream bao gồm ostream đã được thêm vào C ++ 11.

Trong lịch sử, hầu hết các triển khai đã có iostream bao gồm ostream dù sao, có lẽ để dễ thực hiện hoặc có lẽ để cung cấp một QoI tốt hơn.

Nhưng nó sẽ phù hợp với iostream để chỉ xác định ostream lớp mà không xác định operator<< quá tải.


20
2017-08-04 23:22





Điều hơi sai lầm duy nhất mà tôi thấy với chương trình này là bạn không được phép gán một chuỗi ký tự cho một biến thể char con trỏ, mặc dù điều này thường được chấp nhận như một phần mở rộng của trình biên dịch.

Nếu không, chương trình này xuất hiện được xác định rõ ràng với tôi:

  • Các quy tắc quyết định cách mảng ký tự trở thành con trỏ ký tự khi được truyền như các tham số (như với cout << s2) được xác định rõ.
  • Mảng là null-chấm dứt, đó là một điều kiện cho operator<< với một char* (hoặc một const char*).
  • #include <iostream> bao gồm <ostream>, lần lượt xác định operator<<(ostream&, const char*), vì vậy mọi thứ dường như được đặt ra.

13
2017-08-04 18:25





Bạn không thể dự đoán được hành vi của trình biên dịch, vì những lý do đã nêu ở trên. (Nó Nên không biên dịch, nhưng có thể không.)

Nếu biên dịch thành công, thì hành vi được xác định rõ. Bạn chắc chắn có thể dự đoán hành vi của chương trình.

Nếu không biên dịch được thì không có chương trình nào. Trong một ngôn ngữ được biên dịch, chương trình có thể thực thi, không phải mã nguồn. Nếu bạn không có một tập tin thực thi, bạn không có một chương trình, và bạn không thể nói về hành vi của một cái gì đó không tồn tại.

Vì vậy, tôi muốn nói tuyên bố của giáo sư của bạn là sai. Bạn không thể dự đoán được hành vi của trình biên dịch khi phải đối mặt với mã này, nhưng điều đó khác với hành vi của chương trình. Vì vậy, nếu anh ta sẽ chọn nits, anh ta nên chắc chắn rằng anh ấy đúng. Hoặc, tất nhiên, bạn có thể đã đánh giá sai anh ta và sai lầm là trong bản dịch của bạn về những gì anh ta nói.


12
2017-08-05 00:00





Như những người khác đã lưu ý, mã là bất hợp pháp theo C ++ 11, mặc dù nó đã được hợp lệ theo các phiên bản trước đó. Do đó, một trình biên dịch cho C ++ 11 được yêu cầu phát hành ít nhất một chẩn đoán, nhưng hành vi của trình biên dịch hoặc phần còn lại của hệ thống xây dựng không được chỉ rõ ngoài điều đó. Không có gì trong tiêu chuẩn sẽ cấm một trình biên dịch thoát khỏi đột ngột để trả lời lỗi, để lại một tệp đối tượng được viết một phần mà một trình liên kết có thể nghĩ là hợp lệ, cho ra một tệp thực thi bị hỏng.

Mặc dù trình biên dịch tốt phải luôn đảm bảo trước khi nó thoát ra mà bất kỳ tệp đối tượng nào được mong đợi đã tạo ra sẽ hợp lệ, không tồn tại hoặc có thể nhận dạng là không hợp lệ, các vấn đề đó nằm ngoài phạm vi quyền hạn của tiêu chuẩn. Mặc dù trong lịch sử đã có (và vẫn có thể là) một số nền tảng mà một trình biên dịch không thành công có thể dẫn đến các tệp thi hành hợp pháp xuất hiện trong thời gian tùy ý khi được tải (và tôi phải làm việc với các hệ thống có lỗi liên kết thường có hành vi như vậy) , Tôi sẽ không nói rằng hậu quả của lỗi cú pháp thường không thể đoán trước được. Trên một hệ thống tốt, một xây dựng cố gắng nói chung sẽ tạo ra một tệp thực thi với nỗ lực tốt nhất của trình biên dịch khi tạo mã, hoặc sẽ không tạo ra một tệp thực thi nào cả. Một số hệ thống sẽ để lại tệp thực thi cũ sau khi xây dựng không thành công, vì trong một số trường hợp có thể chạy bản dựng thành công cuối cùng có thể hữu ích, nhưng điều đó cũng có thể dẫn đến nhầm lẫn.

Tùy chọn cá nhân của tôi sẽ dành cho các hệ thống dựa trên đĩa để đổi tên tệp đầu ra, để cho phép các trường hợp hiếm hoi khi tệp thực thi đó hữu ích trong khi tránh nhầm lẫn có thể gây nhầm lẫn khi đang chạy mã mới và lập trình nhúng các hệ thống để cho phép một lập trình viên chỉ định cho từng dự án một chương trình sẽ được tải nếu một tệp thực thi hợp lệ không có sẵn dưới tên bình thường [lý tưởng gì đó cho thấy sự thiếu một chương trình có thể sử dụng một cách an toàn]. Một bộ công cụ hệ thống nhúng thường không có cách nào để biết chương trình nên làm gì, nhưng trong nhiều trường hợp, ai đó viết mã "thực" cho một hệ thống sẽ có quyền truy cập vào một số mã kiểm thử phần cứng có thể dễ dàng thích ứng với mục đích. Tôi không biết rằng tôi đã nhìn thấy hành vi đổi tên, tuy nhiên, và tôi biết rằng tôi đã không nhìn thấy hành vi lập trình được chỉ định.


10
2017-08-04 22:26