GCC 6 có một tính năng tối ưu hóa mới : Nó giả định rằng this
luôn không rỗng và tối ưu hóa dựa trên đó.
Việc truyền phạm vi giá trị bây giờ giả định rằng con trỏ này của các hàm thành viên C ++ là khác rỗng. Điều này giúp loại bỏ các kiểm tra con trỏ rỗng thông thường nhưng cũng phá vỡ một số cơ sở mã không tuân thủ (chẳng hạn như Qt-5, Chromium, KDevelop) . Như một công việc tạm thời xung quanh -fno-delete-null-pointer-checks có thể được sử dụng. Có thể xác định mã sai bằng cách sử dụng -fsanifying = undefined.
Tài liệu thay đổi rõ ràng gọi điều này là nguy hiểm vì nó phá vỡ một lượng đáng ngạc nhiên mã được sử dụng thường xuyên.
Tại sao giả định mới này sẽ phá vỡ mã C ++ thực tế? Có những mẫu cụ thể nào mà các lập trình viên bất cẩn hoặc thiếu hiểu biết dựa vào hành vi cụ thể không xác định này không? Tôi không thể tưởng tượng có ai viết if (this == NULL)
vì điều đó quá phi tự nhiên.
Tôi đoán câu hỏi cần được trả lời là tại sao những người có thiện chí lại viết séc ngay từ đầu.
Trường hợp phổ biến nhất có lẽ là nếu bạn có một lớp là một phần của lệnh gọi đệ quy xảy ra tự nhiên.
Nếu bạn có:
struct Node
{
Node* left;
Node* right;
};
trong C, bạn có thể viết:
void traverse_in_order(Node* n) {
if(!n) return;
traverse_in_order(n->left);
process(n);
traverse_in_order(n->right);
}
Trong C ++, thật tuyệt khi đặt đây là một hàm thành viên:
void Node::traverse_in_order() {
// <--- What check should be put here?
left->traverse_in_order();
process();
right->traverse_in_order();
}
Trong những ngày đầu của C ++ (trước khi chuẩn hóa), người ta nhấn mạnh rằng các hàm thành viên là đường cú pháp cho một hàm mà this
tham số là không tường minh. Mã được viết bằng C ++, được chuyển đổi sang C tương đương và được biên dịch. Thậm chí có những ví dụ rõ ràng rằng việc so sánh this
với null là có ý nghĩa và trình biên dịch Cfront ban đầu cũng đã tận dụng điều này. Vì vậy, đến từ nền C, lựa chọn rõ ràng cho séc là:
if(this == nullptr) return;
Lưu ý: Bjarne Stroustrup thậm chí còn đề cập rằng các quy tắc this
đã thay đổi trong những năm ở đây
Và điều này đã hoạt động trên nhiều trình biên dịch trong nhiều năm. Khi tiêu chuẩn hóa xảy ra, điều này đã thay đổi. Và gần đây hơn, các trình biên dịch bắt đầu lợi dụng gọi một hàm thành viên nơi this
con người nullptr
là hành vi không xác định, có nghĩa là tình trạng này luôn luôn là false
, và trình biên dịch là miễn phí để bỏ qua nó.
Điều đó có nghĩa là để thực hiện bất kỳ hành trình nào của cây này, bạn cần phải:
Thực hiện tất cả các bước kiểm tra trước khi gọi traverse_in_order
void Node::traverse_in_order() {
if(left) left->traverse_in_order();
process();
if(right) right->traverse_in_order();
}
Điều này cũng có nghĩa là kiểm tra MỌI trang web cuộc gọi nếu bạn có thể có một gốc rỗng.
Không sử dụng chức năng thành viên
Điều này có nghĩa là bạn đang viết mã kiểu C cũ (có thể là một phương thức tĩnh) và gọi nó với đối tượng một cách rõ ràng như một tham số. ví dụ. bạn quay lại với công việc viết lách Node::traverse_in_order(node);
hơn là node->traverse_in_order();
ở trang web cuộc gọi.
Tôi tin rằng cách dễ nhất / gọn gàng nhất để sửa ví dụ cụ thể này theo cách tuân thủ các tiêu chuẩn là thực sự sử dụng một nút giám sát chứ không phải là một nullptr
.
// static class, or global variable
Node sentinel;
void Node::traverse_in_order() {
if(this == &sentinel) return;
...
}
Cả hai tùy chọn đầu tiên đều không có vẻ hấp dẫn và mặc dù mã có thể thoát khỏi nó, nhưng họ đã viết mã xấu this == nullptr
thay vì sử dụng một bản sửa lỗi thích hợp.
Tôi đoán đó là cách một số cơ sở mã này phát triển để this == nullptr
kiểm tra chúng.
Nó làm như vậy vì mã "thực tế" đã bị hỏng và liên quan đến hành vi không xác định để bắt đầu. Không có lý do gì để sử dụng giá trị rỗng this
, ngoài việc tối ưu hóa vi mô, thường là một giá trị rất sớm.
Đó là một thực tiễn nguy hiểm, vì việc điều chỉnh con trỏ do truyền qua phân cấp lớp có thể biến một giá trị null this
thành một giá trị không rỗng. Vì vậy, ít nhất, lớp có các phương thức được cho là làm việc với null this
phải là lớp cuối cùng không có lớp cơ sở: nó không thể bắt nguồn từ bất cứ thứ gì và cũng không thể bắt nguồn từ đó. Chúng tôi nhanh chóng khởi hành từ vùng đất thực tế đến xấu xí .
Về mặt thực tế, mã không cần phải xấu:
struct Node
{
Node* left;
Node* right;
void process();
void traverse_in_order() {
traverse_in_order_impl(this);
}
private:
static void traverse_in_order_impl(Node * n)
if (!n) return;
traverse_in_order_impl(n->left);
n->process();
traverse_in_order_impl(n->right);
}
};
Nếu bạn có một cây trống (ví dụ: gốc là nullptr), giải pháp này vẫn dựa trên hành vi không xác định bằng cách gọi traverse_in_order với nullptr.
Nếu cây trống, hay còn gọi là null Node* root
, bạn không được phép gọi bất kỳ phương thức không tĩnh nào trên nó. Giai đoạn = Stage. Hoàn toàn tốt nếu có mã cây giống C lấy một con trỏ thể hiện bằng một tham số rõ ràng.
Đối số ở đây dường như sôi sục về việc bằng cách nào đó cần phải viết các phương thức không tĩnh trên các đối tượng có thể được gọi từ một con trỏ thể hiện null. Không cần như vậy. Cách viết mã như vậy của C-with-objects vẫn còn đẹp hơn trong thế giới C ++, bởi vì nó có thể được nhập ít nhất là an toàn. Về cơ bản, null this
là một tối ưu hóa vi mô, với phạm vi sử dụng hẹp như vậy, việc không cho phép nó là IMHO hoàn toàn ổn. Không có API công khai nào nên phụ thuộc vào giá trị rỗng this
.
Tài liệu thay đổi rõ ràng gọi điều này là nguy hiểm vì nó phá vỡ một lượng đáng ngạc nhiên mã được sử dụng thường xuyên.
Tài liệu không gọi nó là nguy hiểm. Nó cũng không tuyên bố rằng nó phá vỡ một lượng mã đáng ngạc nhiên . Nó chỉ đơn giản là chỉ ra một số cơ sở mã phổ biến mà nó tuyên bố được biết là dựa vào hành vi không xác định này và sẽ bị hỏng do thay đổi trừ khi tùy chọn giải pháp thay thế được sử dụng.
Tại sao giả định mới này sẽ phá vỡ mã C ++ thực tế?
Nếu mã c ++ thực tế dựa trên hành vi không xác định, thì các thay đổi đối với hành vi không xác định đó có thể phá vỡ nó. Đây là lý do tại sao UB phải được tránh, ngay cả khi một chương trình dựa vào nó dường như hoạt động như dự định.
Có những mẫu cụ thể nào mà các lập trình viên bất cẩn hoặc thiếu hiểu biết dựa vào hành vi cụ thể không xác định này không?
Tôi không biết liệu nó có phải là anti -pattern lan rộng hay không , nhưng một lập trình viên không có kiến thức có thể nghĩ rằng họ có thể sửa chương trình của mình khỏi bị treo bằng cách:
if (this)
member_variable = 42;
Khi lỗi thực sự đang tham chiếu đến một con trỏ null ở một nơi khác.
Tôi chắc chắn rằng nếu lập trình viên không đủ hiểu biết, họ sẽ có thể đưa ra các phản hồi (anti) -patterns nâng cao hơn dựa trên UB này.
Tôi không thể tưởng tượng có ai viết
if (this == NULL)
vì điều đó quá phi tự nhiên.
Tôi có thể.
Một số mã "thực tế" (cách hài hước để đánh vần "lỗi") đã bị hỏng trông như thế này:
void foo(X* p) {
p->bar()->baz();
}
và nó đã quên tính đến thực tế là p->bar()
đôi khi trả về một con trỏ null, có nghĩa là tham chiếu đến nó để gọi baz()
là không xác định.
Không phải tất cả các mã mà đã bị hỏng chứa rõ ràng if (this == nullptr)
hoặc if (!p) return;
kiểm tra. Một số trường hợp chỉ đơn giản là các hàm không truy cập bất kỳ biến thành viên nào, và do đó, dường như hoạt động OK. Ví dụ:
struct DummyImpl {
bool valid() const { return false; }
int m_data;
};
struct RealImpl {
bool valid() const { return m_valid; }
bool m_valid;
int m_data;
};
template<typename T>
void do_something_else(T* p) {
if (p) {
use(p->m_data);
}
}
template<typename T>
void func(T* p) {
if (p->valid())
do_something(p);
else
do_something_else(p);
}
Trong đoạn mã này khi bạn gọi func<DummyImpl*>(DummyImpl*)
với một con trỏ null, có một tham chiếu "khái niệm" của con trỏ để gọi p->DummyImpl::valid()
, nhưng trên thực tế, hàm thành viên đó chỉ trả về false
mà không cần truy cập *this
. Điều đó return false
có thể được nội tuyến và vì vậy trong thực tế, con trỏ không cần phải được truy cập. Vì vậy, với một số trình biên dịch, nó dường như hoạt động OK: không có mặc định nào cho việc bỏ qua hội nghị là null, p->valid()
là sai, vì vậy mã gọi do_something_else(p)
, kiểm tra con trỏ null và không có gì cả. Không có sự cố hoặc hành vi bất ngờ nào được quan sát.
Với GCC 6, bạn vẫn nhận được lệnh gọi p->valid()
, nhưng trình biên dịch bây giờ suy ra từ biểu thức đó p
phải khác rỗng (nếu không p->valid()
sẽ là hành vi không xác định) và ghi chú thông tin đó. Thông tin suy luận đó được trình tối ưu hóa sử dụng để nếu lệnh gọi do_something_else(p)
được nội tuyến, việc if (p)
kiểm tra hiện được coi là dư thừa, bởi vì trình biên dịch nhớ rằng nó không phải là rỗng, và do đó, nội dòng mã để:
template<typename T>
void func(T* p) {
if (p->valid())
do_something(p);
else {
// inlined body of do_something_else(p) with value propagation
// optimization performed to remove null check.
use(p->m_data);
}
}
Điều này bây giờ thực sự không tham chiếu đến một con trỏ null, và do đó mã trước đây dường như hoạt động ngừng hoạt động.
Trong ví dụ này, lỗi nằm trong func
, đáng lẽ phải kiểm tra null trước (hoặc người gọi không bao giờ nên gọi nó bằng null):
template<typename T>
void func(T* p) {
if (p && p->valid())
do_something(p);
else
do_something_else(p);
}
Một điểm quan trọng cần nhớ là hầu hết các tối ưu hóa như thế này không phải là trường hợp trình biên dịch nói "à, lập trình viên đã kiểm tra con trỏ này chống lại null, tôi sẽ loại bỏ nó chỉ để gây phiền nhiễu". Điều gì sẽ xảy ra là các tối ưu hóa hàng loạt khác nhau như nội dòng và truyền phạm vi giá trị kết hợp với nhau để làm cho những kiểm tra đó trở nên dư thừa, bởi vì chúng đến sau một kiểm tra trước đó hoặc một lần tham khảo. Nếu trình biên dịch biết rằng một con trỏ khác rỗng tại điểm A trong một hàm và con trỏ không bị thay đổi trước một điểm B sau đó trong cùng một hàm, thì nó sẽ biết nó cũng không null tại B. Khi nội tuyến xảy ra các điểm A và B thực sự có thể là các đoạn mã ban đầu nằm trong các hàm riêng biệt, nhưng bây giờ được kết hợp thành một đoạn mã và trình biên dịch có thể áp dụng kiến thức của nó rằng con trỏ không phải là null ở nhiều nơi hơn. Đây là một cách tối ưu hóa cơ bản, nhưng rất quan trọng, và nếu các trình biên dịch không làm điều đó thì mã hàng ngày sẽ chậm hơn đáng kể và mọi người sẽ phàn nàn về các nhánh không cần thiết để kiểm tra lại các điều kiện giống nhau nhiều lần.
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.
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ì?
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.
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.
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!
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.
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?
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.
Trump và Giám đốc điều hành GM Mary Barra vào năm 2017. Kể từ khi tin tức nổ ra ngày hôm qua rằng General Motors đang cắt giảm việc làm ở Bắc Mỹ để chuẩn bị cho tương lai điện / xe tự hành / khủng khiếp có thể xảy ra của chúng tôi, gần như tất cả mọi người đã tự hỏi: Tổng thống Trump, người đã vận động tranh cử trên một nền tảng sẽ như thế nào của việc mang lại và duy trì việc làm ô tô của Mỹ, phải nói gì về điều này? Bây giờ chúng ta biết.
Bethesda cho biết ngày hôm nay trên Reddit, Fallout 76 sẽ có một bản vá lớn khác vào ngày 4 tháng 12, sẽ bắt đầu cố gắng khắc phục một số vấn đề lớn hơn của trò chơi. Ngoài ra, công ty cho biết họ có kế hoạch giao tiếp nhiều hơn trong tương lai về những gì nhóm Fallout 76 đang làm việc và khi nào các bản cập nhật mới cho trò chơi sẽ ra mắt.
Bạn có biết rằng một trong sáu bệnh nhân ở bệnh viện ở Hoa Kỳ được điều trị tại một cơ sở Công giáo? Và bạn có biết rằng quyền sở hữu của Công giáo hạn chế sâu sắc việc thiết lập các thủ tục y tế có thể được thực hiện trong các bệnh viện này, rõ ràng nhất là khi nói đến chăm sóc sức khỏe sinh sản? Bây giờ NPR báo cáo rằng sự gia tăng của các bệnh viện thuộc Công giáo đang gây khó khăn hơn cho các bác sĩ — những người bị cấm bởi các quy định do Hoa Kỳ viết.
Đường chân trời San Francisco bị che khuất bởi khói và khói mù do cháy rừng phía sau Đảo Alcatraz, Thứ Tư, ngày 14 tháng 11 năm 2018, ở San Francisco.
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. 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ì!
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
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 +"
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.
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.
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.
Tôi ghét từ "tàu đắm". Mọi người cảm thấy thoải mái trong la bàn đạo đức của riêng mình, và khi làm như vậy, họ thấy mình vượt qua sự phán xét.