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


108

Tôi hiện đang học các 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;
}

Anh ấy đã viết trong phần bình luận rằng chúng tôi không thể đoán được hành vi của chương trình. Chính xác thì điều gì khiến nó không thể đoán trước được? Tôi thấy không có gì sai với nó.


2
Bạn có chắc mình đã sao chép đúng mã của giáo sư không? Mặc dù chính thức có thể lập luận 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 làm như vậy là vô nghĩa. Và tôi nghi ngờ rằng bất kỳ giáo sư nào sẽ sử dụng một cái gì đó bí truyền để minh họa "không thể đoán trước" cho sinh viên.
AnT

1
@Lightness Races in Orbit: Các trình biên dịch được phép "chấp nhận" mã không hợp lệ sau khi đưa ra các thông báo chẩn đoán được yêu cầu. Nhưng đặc tả ngôn ngữ không xác định hành vi của mã. Tức là do 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

2
@TheParamagneticCroissant: Không. Việc khởi tạo chưa được hình thành trong thời hiện đại.
Các cuộc đua ánh sáng trong quỹ đạo

2
@The Paramagnetic Croissant: Như tôi đã nói ở trên, ngôn ngữ này không yêu cầu mã sai để "không biên dịch được". Các trình biên dịch chỉ cần đưa ra chẩn đoán. Sau đó, họ được phép tiếp tục và biên dịch "thành công" mã. Tuy nhiên, hành vi của mã như vậy không được định nghĩa bởi đặc tả ngôn ngữ.
AnT

2
Tôi rất muốn biết câu trả lời mà giáo sư của bạn đã cho bạn là gì.
Daniël W. Crompton

Câu trả lời:


125

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

char* s = "My String";

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

Dòng đúng là:

const char* s = "My String";

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


10
với -pedantic nó: main.cpp: 6: 16: cảnh báo: ISO C ++ cấm chuyển đổi một chuỗi liên tục để 'char *' [-Wpedantic]
marcinj

17
@black: Không, thực tế là việc chuyển đổi là bất hợp pháp khiến chương trình trở nên không hợp lệ. Nó đã không được chấp nhận trong quá khứ . Chúng ta không còn trong quá khứ.
Các cuộc đua ánh sáng trong quỹ đạo vào

17
(Đó là ngớ ngẩn bởi vì đó là mục đích của việc không dùng kéo dài 12 năm)
Lightness Races ở Orbit

17
@black: Hành vi của một chương trình không hợp lệ không được "xác định hoàn hảo".
Các cuộc đua ánh sáng trong quỹ đạo vào

11
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.
Các cuộc đua ánh sáng trong quỹ đạo

81

Câu trả lời là: nó phụ thuộc vào tiêu chuẩn C ++ bạn đang biên dịch. Tất cả các mã đều được định dạng 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ó kiểu const char[10]và chúng tôi đang cố gắng khởi tạo một con trỏ không phải const tới nó. Đối với tất cả các kiểu khác ngoài charhọ chuỗi ký tự, việc khởi tạo như vậy luôn là bất hợp pháp. Ví dụ:

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

Tuy nhiên, trước C ++ 11, đối với các ký tự chuỗi, 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ự rộng có thể được chuyển đổi thành một giá trị của kiểu “ con trỏ đến ký tự ”; [...]. 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. Việc chuyển đổi này chỉ được xem xét khi có một loại mục tiêu con trỏ thích hợp rõ ràng, chứ không phải khi có nhu cầu chung là chuyển đổi từ giá trị sang giá trị. [Lưu ý: chuyển đổi này không được dùng nữa . Xem Phụ lục D. ]

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

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

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

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

Với điều đó, phần còn lại của chương trình hoàn toàn ổn vì bạn thực sự không bao giờ chạm vào snữa. Việc đọc một constđối tượng đã tạo thông qua một constcon trỏ không phải là hoàn toàn OK. Viết một constđối tượng đã tạo qua một 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 snào thông qua bất kỳ nơi nào trong mã của bạn, chương trình sử dụng tốt trong C ++ 03, không thể biên dịch trong C ++ 11 nhưng dù sao thì cũng có - và do trình biên dịch cho phép, vẫn không có hành vi không xác định trong đó † . Với sự cho phép mà các trình biên dịch vẫn [không chính xác] diễn giải các quy tắc C ++ 03, tôi không thấy điều gì có thể dẫn đến hành vi "không thể đoán trước". Viết cho smặc dù, và tất cả các cược đã tắt. Trong cả C ++ 03 và C ++ 11.


† Tuy nhiên, một lần nữa, theo định nghĩa, mã không hợp lệ không mang lại kỳ vọng về hành vi hợp lý
‡ Ngoại trừ không, hãy xem câu trả lời của Matt McNabb


Tôi nghĩ rằng ở đây "không thể đoán trước" được mục đích của giáo sư có nghĩa là người ta không thể sử dụng tiêu chuẩn để dự đoán những gì một trình biên dịch sẽ làm với mã sai (ngoài việc đưa ra chẩn đoán). Vâng, nó có thể coi nó như C ++ 03 nói rằng nó nên được xử lý, và (có nguy cơ mắc lỗi ngụy biện "Không đúng Scotsman") cho phép chúng ta dự đoán với một số niềm tin rằng đây là điều duy nhất mà một người viết trình biên dịch hợp lý. sẽ bao giờ chọn nếu mã biên dịch hoàn toàn. Sau đó, một lần nữa, nó có thể coi nó như một ý nghĩa để đảo ngược chuỗi ký tự trước khi truyền nó thành không phải const. C ++ tiêu chuẩn không quan tâm.
Steve Jessop

2
@SteveJessop Tôi không mua cách giải thích đó. Đây không phải là hành vi không xác định cũng không phải là loại mã sai mà các nhãn tiêu chuẩn không yêu cầu chẩn đoán. Đó là một vi phạm hệ thống kiểu đơn giản nên rất dễ đoán (biên dịch và thực hiện những việc bình thường trên C ++ 03, không biên dịch được trên C ++ 11). Bạn thực sự không thể sử dụng lỗi trình biên dịch (hoặc giấy phép nghệ thuật) để cho rằng mã là không thể đoán trước - nếu không, tất cả mã về mặt tautology 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 liệu tiêu chuẩn có xác định hành vi (nếu có) của mã hay không. Tôi nghi ngờ giáo sư cũng đang làm như vậy, và "không thể đoán trước" chỉ là một cách nói mang tính chất ham chơi rằng tiêu chuẩn hiện tại không xác định hành vi. Dù sao thì điều đó có vẻ dễ xảy ra hơn với tôi, hơn là giáo sư tin tưởng không chính xác rằng đây là một chương trình được xây dựng tốt với hành vi không xác định.
Steve Jessop

1
Không nó không. Tiêu chuẩn không xác định hành vi của các chương trình không hợp lệ.
Steve Jessop

1
@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ĩ lý do chính mà tiêu chuẩn không chỉ định hành vi của các chương trình không hợp lệ là để 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ác cú pháp không được định dạng tốt (như Objective C). Cho phép việc triển khai để làm cho tổng số đống rác trong quá trình dọn dẹp sau khi biên dịch thất bại chỉ là một phần thưởng :-)
Steve Jessop

20

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

Tuy nhiên, chương trình cũng không được hình thành trước C ++ 11.

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

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

Nhưng sẽ phù hợp nếu iostreamchỉ định nghĩa ostreamlớp mà không xác định các operator<<quá tải.


13

Điều hơi sai 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 charcon trỏ có thể thay đổi , mặc dù điều này thường được chấp nhận như một phần mở rộng trình biên dịch.

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

  • Các quy tắc chỉ định cách mảng ký tự trở thành con trỏ ký tự khi được truyền dưới dạng tham số (chẳng hạn như với cout << s2) được xác định rõ ràng.
  • Mảng được kết thúc bằng null, đây là điều kiện đối operator<<với một char*(hoặc a 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ư đúng vị trí.

12

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

Nếu quá trình 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 nó không biên dịch được, thì không có chương trình. Trong ngôn ngữ biên dịch, chương trình là tệp thực thi, không phải mã nguồn. Nếu bạn không có tệp thực thi, bạn không có chương trình và bạn không thể nói về hành vi của một thứ không tồn tại.

Vì vậy, tôi muốn nói rằng tuyên bố của bạn là sai. Bạn không thể dự đoán hành vi của trình biên dịch khi đố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 ấy định chọn nits, anh ấy tốt hơn nên chắc chắn rằng mình đúng. Hoặc, tất nhiên, bạn có thể đã trích dẫn sai cho anh ấy và lỗi là ở bản dịch của bạn những gì anh ấy nói.


10

Như những người khác đã lưu ý, mã này không hợp lệ trong C ++ 11, mặc dù nó có hiệu lực trong các phiên bản trước đó. Do đó, trình biên dịch cho C ++ 11 được yêu cầu đưa ra í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 xác định ngoài điều đó. Không có điều gì trong Tiêu chuẩn cấm trình biên dịch thoát đột ngột khi gặp lỗi, để lại tệp đối tượng được ghi một phần mà trình liên kết có thể nghĩ là hợp lệ, dẫn đến tệp thực thi bị hỏng.

Mặc dù một trình biên dịch tốt phải luôn đảm bảo trước khi thoát ra rằng bất kỳ tệp đối tượng nào mà nó dự kiến ​​tạo ra sẽ hợp lệ, không tồn tại hoặc có thể nhận ra là không hợp lệ, các vấn đề này nằm ngoài phạm vi quyền hạn của Tiêu chuẩn. Mặc dù trước đây đã có (và có thể vẫn còn) một số nền tảng mà quá trình biên dịch không thành công có thể dẫn đến các tệp thực thi xuất hiện hợp pháp bị lỗi khi tải (và tôi đã phải làm việc với các hệ thống mà 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 nói chung là không thể đoán trước. Trên một hệ thống tốt, một bản dựng được 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 trong việc tạo mã, hoặc sẽ không tạo ra 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 thất bại,

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

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.