Một chương trình C ++ có nên nắm bắt tất cả các ngoại lệ và ngăn chặn các ngoại lệ xuất hiện trong quá khứ () không?


29

Tôi đã từng được khuyên rằng một chương trình C ++ cuối cùng sẽ nắm bắt được tất cả các ngoại lệ. Lý do được đưa ra vào thời điểm đó về cơ bản là các chương trình cho phép các ngoại lệ nổi lên bên ngoài khi main()bước vào trạng thái zombie kỳ lạ. Tôi đã được nói điều này vài năm trước và khi nhìn lại tôi tin rằng hiện tượng quan sát được là do sự phát sinh dài của các bãi lõi lớn đặc biệt từ dự án đang được đề cập đến.

Vào thời điểm này có vẻ kỳ quái nhưng có sức thuyết phục. Điều hoàn toàn vô lý là C ++ nên "trừng phạt" các lập trình viên vì không nắm bắt được tất cả các ngoại lệ nhưng bằng chứng trước mắt tôi dường như đã chứng minh điều này. Đối với dự án đang được đề cập, các chương trình đưa ra các ngoại lệ chưa được phát hiện dường như đã đi vào trạng thái zombie kỳ lạ - hoặc như tôi nghi ngờ nguyên nhân là bây giờ, một quá trình ở giữa một bãi chứa lõi không mong muốn khó có thể dừng lại.

(Đối với bất kỳ ai thắc mắc tại sao điều này không rõ ràng hơn vào thời điểm đó: Dự án đã tạo ra một lượng lớn đầu ra trong nhiều tệp từ nhiều quy trình che khuất một cách hiệu quả bất kỳ loại aborted (core dumped)thông báo nào và trong trường hợp cụ thể này, kiểm tra sau xử lý các bãi rác lõi Đây không phải là một kỹ thuật sửa lỗi quan trọng nên các vấn đề cốt lõi không được suy nghĩ nhiều. Các vấn đề với một chương trình thường không phụ thuộc vào trạng thái được tích lũy từ nhiều sự kiện theo thời gian bởi một chương trình tồn tại lâu dài mà là các đầu vào ban đầu cho một chương trình tồn tại ngắn (< 1 giờ) vì vậy sẽ thực tế hơn khi chỉ chạy lại một chương trình có cùng các đầu vào từ bản dựng gỡ lỗi hoặc trong trình gỡ lỗi để có thêm thông tin.)

Hiện tại, tôi không chắc chắn liệu có bất kỳ lợi thế hay bất lợi lớn nào trong việc nắm bắt các ngoại lệ chỉ nhằm mục đích ngăn chặn các ngoại lệ rời đi main().

Ưu điểm nhỏ mà tôi có thể nghĩ đến khi cho phép các ngoại lệ nổi lên trong quá khứ main()là nó gây ra kết quả std::exception::what()được in ra thiết bị đầu cuối (ít nhất là với các chương trình được biên dịch gcc trên Linux). Mặt khác, điều này là tầm thường để đạt được bằng cách thay vào đó nắm bắt tất cả các ngoại lệ xuất phát từ std::exceptionvà in kết quả std::exception::what()và nếu muốn in một tin nhắn từ một ngoại lệ không xuất phát từ std::exceptionđó thì phải bắt trước khi rời đi main()để in thông điệp.

Nhược điểm khiêm tốn mà tôi có thể nghĩ đến khi cho phép các ngoại lệ nổi lên trong quá khứ main()là các bãi chứa lõi không mong muốn có thể được tạo ra. Đối với một quá trình sử dụng một lượng lớn bộ nhớ, điều này có thể gây phiền toái và kiểm soát hành vi bán phá giá lõi từ một chương trình yêu cầu các cuộc gọi chức năng dành riêng cho hệ điều hành. Mặt khác, nếu muốn có một kết xuất và thoát lõi, thì điều này thay vào đó có thể đạt được bất cứ lúc nào bằng cách gọi std::abort()và một lối thoát không có kết xuất lõi có thể đạt được bất cứ lúc nào bằng cách gọi std::exit().

Thông thường, tôi không nghĩ rằng tôi đã từng thấy what(): ...tin nhắn mặc định được in bởi một chương trình phân phối rộng rãi khi gặp sự cố.

Điều gì, nếu có, là những lý lẽ mạnh mẽ cho hoặc chống lại việc cho phép các ngoại lệ C ++ nổi lên trong quá khứ main()?

Chỉnh sửa: Có rất nhiều câu hỏi xử lý ngoại lệ chung trên trang web này. Câu hỏi của tôi là cụ thể về các trường hợp ngoại lệ C ++ không thể xử lý và đã thực hiện mọi cách main()- có thể một thông báo lỗi có thể được in nhưng đó là lỗi dừng hiển thị ngay lập tức.




@gnat Câu hỏi của tôi cụ thể hơn một chút. Đó là về các ngoại lệ C ++ không thể xử lý thay vì xử lý ngoại lệ trong bất kỳ ngôn ngữ lập trình nào trong mọi trường hợp.
Praxeolitic

Đối với các chương trình đa luồng, mọi thứ có thể phức tạp hơn hoặc kỳ lạ hơn (ngoại lệ wrt)
Basile Starynkevitch

1
Những người làm phần mềm cho Ariane 5 chắc chắn ước họ đã bắt được tất cả các ngoại lệ trong lần ra mắt đầu tiên ...
Eric Towers

Câu trả lời:


25

Một vấn đề với việc để ngoại lệ đi qua chính là chương trình sẽ kết thúc bằng một cuộc gọi std::terminatemà hành vi mặc định sẽ gọi std::abort. Nó chỉ được thực hiện được xác định nếu việc terminatehủy bỏ ngăn xếp được thực hiện trước khi gọi để chương trình của bạn có thể kết thúc mà không cần gọi một hàm hủy! Nếu bạn có một số tài nguyên thực sự cần được khôi phục bằng lệnh gọi hàm hủy, bạn đang ở trong một ...


12
Nếu bạn có một số tài nguyên thực sự cần được khôi phục bằng lệnh gọi hàm hủy, thì bạn đang ở trong một dưa chua ... => Cho rằng (thật không may) C ++ khá dễ bị sập, và điều đó không đề cập đến việc HĐH có thể quyết định giết chương trình của bạn bất cứ lúc nào (ví dụ kẻ giết người OOM trong Unix), chương trình của bạn (và môi trường của nó) cần được điều chỉnh để hỗ trợ sự cố.
Matthieu M.

@MatthieuM. Bạn đang nói rằng các tàu khu trục không phải là một nơi tốt để làm sạch tài nguyên nên xảy ra ngay cả trong một vụ tai nạn?
Praxeolitic

6
@Praxeolitic: Tôi đang nói rằng không phải tất cả các sự cố đều làm mất ngăn xếp, ví dụ std::abortnhư không và do đó, các xác nhận thất bại cũng không. Cũng đáng lưu ý rằng trên các HĐH hiện đại, bản thân HĐH sẽ dọn sạch rất nhiều tài nguyên (bộ nhớ, xử lý tệp, ...) được gắn với ID tiến trình. Cuối cùng, tôi cũng đã gợi ý về "Defense in Depth": không đáng tin khi tất cả các quy trình khác đều không có lỗi và sẽ luôn giải phóng các tài nguyên mà họ có được (phiên, hoàn thành tốt việc viết tệp, ...) bạn phải lập kế hoạch cho nó ...
Matthieu M.

3
@Praxeolitic Không, phần mềm của bạn cần không làm hỏng dữ liệu trong khi mất điện. Và nếu bạn có thể quản lý điều đó, bạn cũng có thể quản lý để không làm hỏng dữ liệu sau một ngoại lệ chưa được xử lý.
dùng253751

1
@Praxeolitic: Thật ra, cái này không tồn tại. Bạn sẽ muốn nghe WM_POWERBROADCASTtin nhắn. Điều này chỉ hoạt động nếu máy tính của bạn chạy bằng pin (nếu bạn đang sử dụng máy tính xách tay hoặc UPS).
Brian

28

Lý do chính cho việc không để ngoại lệ thoát khỏi mainlà vì nếu không, bạn sẽ mất tất cả khả năng kiểm soát cách vấn đề được báo cáo cho người dùng của bạn.

Đối với một chương trình không được sử dụng trong một thời gian dài hoặc được phân phối rộng rãi, có thể chấp nhận rằng các lỗi không mong muốn được báo cáo theo bất kỳ cách nào mà HĐH quyết định thực hiện (ví dụ: hiển thị hộp thoại lỗi trong khuôn mặt của bạn trên Windows ).

Đối với các chương trình mà bạn bán hoặc được cung cấp cho công chúng bởi một tổ chức có uy tín để duy trì, nói chung nên báo cáo theo cách tốt đẹp mà bạn gặp phải sự cố không mong muốn và cố gắng tiết kiệm càng nhiều dữ liệu người dùng càng tốt. Không mất nửa ngày làm việc của người dùng của bạn và không gặp sự cố bất ngờ, nhưng tắt bán một cách duyên dáng thường tốt hơn nhiều cho danh tiếng doanh nghiệp của bạn so với giải pháp thay thế.


Bạn có chắc chắn hành vi mặc định của một ngoại lệ C ++ main() có liên quan đến HĐH không? HĐH có thể không biết gì về C ++. Tôi đoán là nó được xác định bởi mã trình biên dịch chèn vào đâu đó vào chương trình.
Praxeolitic

5
@Praxeolitic: Thêm nữa là hệ điều hành đặt các quy ước và trình biên dịch tạo mã để thực hiện các quy ước đó khi một chương trình chấm dứt bất ngờ. Điểm mấu chốt là nên tránh việc chấm dứt chương trình bất ngờ bất cứ khi nào có thể.
Bart van Ingen Schenau

Bạn chỉ mất kiểm soát trong phạm vi của std C ++. Một cách cụ thể thực hiện để xử lý các "sự cố" như vậy nên có sẵn. C ++ thường không chạy trong chân không.
Martin Ba

Hộp thoại lỗi trong khuôn mặt của bạn có thể được định cấu hình bật / tắt trong sổ đăng ký để biết giá trị của nó - nhưng bạn sẽ cần kiểm soát sổ đăng ký để thực hiện việc này - và hầu hết các chương trình không chạy trong chế độ kiosk (đã tiếp quản HĐH).
PerryC

11

TL; DR : Thông số kỹ thuật nói gì?


Một đường vòng kỹ thuật ...

Khi một ngoại lệ được đưa ra và không có trình xử lý nào sẵn sàng cho nó:

  • đó là việc thực hiện được xác định cho dù ngăn xếp có mở ra hay không
  • std::terminate được gọi, mà theo mặc định hủy bỏ
  • tùy thuộc vào thiết lập môi trường của bạn, việc hủy bỏ có thể hoặc không thể để lại báo cáo sự cố

Cái sau có thể hữu ích cho các lỗi rất không thường xuyên (vì tái tạo chúng là một quá trình lãng phí thời gian).


Liệu có bắt được tất cả các ngoại lệ hay không, cuối cùng, là một vấn đề về đặc điểm kỹ thuật:

  • được chỉ định làm thế nào để báo cáo lỗi người dùng? (giá trị không hợp lệ, ...)
  • được chỉ định làm thế nào để báo cáo lỗi chức năng? (thiếu thư mục đích, ...)
  • được chỉ định làm thế nào để báo cáo lỗi kỹ thuật? (khẳng định bắn lên, ...)

Đối với bất kỳ chương trình sản xuất nào, điều này nên được chỉ định và bạn nên tuân theo đặc điểm kỹ thuật (và có thể lập luận rằng nó sẽ được thay đổi).

Để nhanh chóng kết hợp các chương trình chỉ được sử dụng bởi những người kỹ thuật (bạn, đồng đội của bạn), thì cũng không sao. Tôi khuyên bạn nên để nó bị sập và thiết lập môi trường để nhận báo cáo hay không tùy thuộc vào nhu cầu của bạn.


1
Điều này. Các yếu tố quyết định wrt. Về cơ bản nằm ngoài phạm vi của C ++.
Martin Ba

4

Một ngoại lệ mà bạn bắt được sẽ cho bạn cơ hội in một thông báo lỗi hay thậm chí cố gắng khắc phục lỗi (có thể chỉ bằng cách khởi chạy lại ứng dụng).

Tuy nhiên, trong C ++, một ngoại lệ không chứa thông tin về trạng thái chương trình khi nó bị ném. Nếu bạn bắt được nó, tất cả trạng thái như vậy sẽ bị quên, trong khi đó nếu bạn để chương trình bị sập, trạng thái thường vẫn ở đó và có thể được đọc từ kết xuất chương trình, giúp gỡ lỗi dễ dàng hơn.

Vì vậy, đó là một sự đánh đổi.


4

Khoảnh khắc bạn biết rằng bạn phải hủy bỏ, hãy tiếp tục và gọi điện std::terminateđể giảm bớt thiệt hại.

Nếu bạn biết bạn có thể thư giãn một cách an toàn, thay vào đó hãy làm điều đó. Hãy nhớ rằng việc hủy xếp chồng không được đảm bảo khi một ngoại lệ không bao giờ bị bắt, vì vậy hãy bắt và suy nghĩ lại.

Nếu bạn có thể báo cáo / ghi lại lỗi một cách an toàn tốt hơn hệ thống sẽ tự khắc phục, hãy tiếp tục.
Nhưng hãy thực sự chắc chắn không vô tình làm cho vấn đề tồi tệ hơn trong khi làm như vậy.

Thường thì quá muộn để lưu dữ liệu khi bạn phát hiện ra một lỗi không thể phục hồi, mặc dù nó phụ thuộc vào lỗi cụ thể.
Dù sao, nếu chương trình của bạn được viết để phục hồi nhanh, chỉ cần giết nó có thể là cách tốt nhất để kết thúc chương trình ngay cả khi đó chỉ là tắt máy bình thường.


1

Đâm vỡ duyên dáng là một điều tốt trong hầu hết thời gian - nhưng có sự đánh đổi. Đôi khi đó là một điều tốt để sụp đổ. Tôi nên đề cập rằng tôi chủ yếu nghĩ về việc gỡ lỗi trong rất lớn. Đối với một chương trình đơn giản - mặc dù điều này vẫn có thể hữu ích, nhưng nó không hữu ích bằng chương trình rất phức tạp (hoặc một số chương trình phức tạp tương tác).

Bạn không muốn gặp sự cố ở nơi công cộng (mặc dù điều này thực sự không thể tránh khỏi - sự cố chương trình và một chương trình không sự cố có thể kiểm chứng bằng toán học thực sự không phải là điều chúng ta đang nói ở đây). Hãy nghĩ về Bill Gates BSODing ở giữa bản demo - tệ, phải không? Tuy nhiên, chúng ta có thể cố gắng tìm ra lý do tại sao chúng ta gặp sự cố và không gặp sự cố một lần nữa theo cùng một cách.

Tôi đã bật một tính năng của Báo cáo Lỗi Windows để tạo ra các sự cố cục bộ đối với các ngoại lệ chưa được xử lý. Nó hoạt động kỳ diệu nếu bạn có các tệp biểu tượng được liên kết với bản dựng (sự cố) của bạn. Thật thú vị, vì tôi đã thiết lập công cụ này, tôi muốn sụp đổ nhiều hơn - bởi vì tôi học được từ mỗi sự cố. Sau đó tôi có thể sửa các lỗi và ít sự cố hơn.

Vì vậy, bây giờ, tôi muốn các chương trình của tôi ném tất cả lên đỉnh và sụp đổ. Trong tương lai, tôi có thể muốn ăn một cách duyên dáng tất cả các ngoại lệ của mình - nhưng không phải nếu tôi có thể làm cho chúng hoạt động cho tôi.

Bạn có thể đọc thêm về Đổ đổ cục bộ tại đây nếu bạn quan tâm: Thu thập kết xuất chế độ người dùng


-6

Cuối cùng, nếu một ngoại lệ xuất hiện trên main (), nó sẽ đánh sập ứng dụng của bạn và trong tâm trí tôi, một ứng dụng sẽ không bao giờ bị sập. Nếu nó ổn để đánh sập một ứng dụng ở một nơi, thì tại sao không phải là bất cứ nơi nào? Tại sao phải xử lý ngoại lệ? (Sarcasm, không thực sự gợi ý điều này ...)

Bạn có thể có một thử / bắt toàn cầu in một thông điệp tao nhã cho người dùng biết rằng đã xảy ra lỗi không thể khắc phục và họ cần gửi email cho IT về nó hoặc một cái gì đó tương tự, nhưng sự cố là hoàn toàn không chuyên nghiệp và không thể chấp nhận được. Việc ngăn chặn nó không quan trọng và bạn có thể thông báo cho người dùng về những gì vừa xảy ra và cách khắc phục để nó không xảy ra lần nữa.

Tai nạn là giờ nghiệp dư.


8
Vì vậy, tốt hơn là giả vờ tất cả mọi thứ đang hoạt động đúng và ghi đè lên tất cả các lưu trữ vĩnh viễn với tiếng vô nghĩa thay thế?
Ded

1
@Ded repeatator: Không: bạn luôn có thể bắt ngoại lệ và xử lý nó.
Giorgio

5
Nếu bạn biết cách xử lý nó, có lẽ bạn sẽ xử lý nó rất lâu trước khi bạn bắt kịp main. Nếu bạn không biết cách xử lý nó, giả vờ bạn làm rõ ràng là một lỗi thực sự khủng khiếp.
Ded repeatator

8
nhưng sự cố là hoàn toàn không chuyên nghiệp và không thể chấp nhận được là kinh tế hơn.
Matthieu M.

Matthieu M. nếu bạn thực sự mất nhiều công sức để ghi lại lỗi, lưu bất kỳ tệp đang mở nào và vẽ một thông điệp thân thiện trên màn hình, tôi không biết phải nói gì. Nếu bạn nghĩ rằng thật vô ích khi thất bại một cách duyên dáng, có lẽ bạn nên nói chuyện với bất cứ ai đang hỗ trợ công nghệ cho mã của bạn.
Roger Hill
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.