Tại sao trình tối ưu hóa GCC 6 nâng cao phá vỡ mã C ++ thực tế?

148
boot4life 2016-04-28 04:45.

GCC 6 có một tính năng tối ưu hóa mới : Nó giả định rằng thisluô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.

4 answers

87
jtlim 2016-04-28 05:03.

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à thistham 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 thisvớ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 thiscon người nullptrlà 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 == nullptrthay 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 == nullptrkiểm tra chúng.

65
Unslander Monica 2016-04-28 04:58.

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 thisthà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 thisphả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 thislà 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.

35
eerorika 2016-04-28 05:12.

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ể.

25
Jonathan Wakely 2016-04-29 03:47.

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ề falsemà không cần truy cập *this. Điều đó return falsecó 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 đó pphả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.

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.

Đây là Tổng thống Trump đe dọa 'trợ cấp' xe điện của GM vì đóng cửa nhà máy

Đây là Tổng thống Trump đe dọa 'trợ cấp' xe điện của GM vì đóng cửa nhà máy

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.

Bản vá lỗi tiếp theo của Fallout 76 sẽ mang lại một số bản sửa lỗi cần thiết

Bản vá lỗi tiếp theo của Fallout 76 sẽ mang lại một số bản sửa lỗi cần thiế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.

Mạng lưới bệnh viện Công giáo sẽ không cho phép nhân viên phá thai tại phòng khám ngoài giờ làm việc

Mạng lưới bệnh viện Công giáo sẽ không cho phép nhân viên phá thai tại phòng khám ngoài giờ làm việc

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.

Khói từ đám cháy rừng ở California đã đến tận thành phố New York

Khói từ đám cháy rừng ở California đã đến tận thành phố New York

Đườ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 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 +"

Đườ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.

Khi bạn không thể trở thành người mà Internet muốn bạn trở thành

Khi bạn không thể trở thành người mà Internet muốn bạn trở thành

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.

Language