2009-03-05 43 views
13

Tôi đang viết thư viện mà tôi muốn di chuyển. Vì vậy, nó không nên phụ thuộc vào glibc hoặc phần mở rộng của Microsoft hoặc bất cứ điều gì khác mà không phải là trong tiêu chuẩn. Tôi có một hệ thống phân cấp tốt đẹp của các lớp học bắt nguồn từ std :: exception mà tôi sử dụng để xử lý các lỗi trong logic và đầu vào. Biết rằng một loại ngoại lệ cụ thể được ném vào một tệp cụ thể và số dòng là hữu ích, nhưng biết cách thực hiện ở đó sẽ có khả năng có giá trị hơn rất nhiều, vì vậy tôi đã tìm cách thu thập dấu vết ngăn xếp.Dấu vết ngăn xếp C++ di động khi ngoại lệ

Tôi biết rằng dữ liệu này có sẵn khi xây dựng chống lại glibc sử dụng các hàm trong execinfo.h (xem question 76822) và thông qua giao diện StackWalk trong triển khai C++ của Microsoft (xem question 126450), nhưng tôi rất muốn tránh bất cứ điều gì đó không phải là di động.

Tôi đã nghĩ đến việc thực hiện chức năng này bản thân mình theo hình thức này:

class myException : public std::exception 
{ 
public: 
    ... 
    void AddCall(std::string s) 
    { m_vCallStack.push_back(s); } 
    std::string ToStr() const 
    { 
    std::string l_sRet = ""; 
    ... 
    l_sRet += "Call stack:\n"; 
    for(int i = 0; i < m_vCallStack.size(); i++) 
     l_sRet += " " + m_vCallStack[i] + "\n"; 
    ... 
    return l_sRet; 
    } 
private: 
    ... 
    std::vector<std::string> m_vCallStack; 
}; 

ret_type some_function(param_1, param_2, param_3) 
{ 
    try 
    { 
    ... 
    } 
    catch(myException e) 
    { 
    e.AddCall("some_function(" + param_1 + ", " + param_2 + ", " + param_3 + ")"); 
    throw e; 
    } 
} 

int main(int argc, char * argv[]) 
{ 
    try 
    { 
    ... 
    } 
    catch (myException e) 
    { 
    std::cerr << "Caught exception: \n" << e.ToStr(); 
    return 1; 
    } 
    return 0; 
} 

Đây có phải là một ý tưởng khủng khiếp? Nó sẽ có nghĩa là rất nhiều công việc thêm thử/bắt khối để mọi chức năng, nhưng tôi có thể sống với điều đó. Nó sẽ không hoạt động khi nguyên nhân của ngoại lệ là lỗi bộ nhớ hoặc thiếu bộ nhớ, nhưng tại thời điểm đó bạn đang khá nhiều hơi say anyway. Nó có thể cung cấp thông tin gây hiểu lầm nếu một số chức năng trong ngăn xếp không bắt ngoại lệ, thêm chính họ vào danh sách và truy lại, nhưng ít nhất tôi cũng có thể đảm bảo rằng tất cả các chức năng thư viện của tôi đều làm như vậy. Không giống như một dấu vết ngăn xếp "thực" tôi sẽ không nhận được số dòng trong các chức năng gọi điện thoại, nhưng ít nhất tôi sẽ có một cái gì đó.

Mối quan tâm chính của tôi là khả năng điều này sẽ làm chậm lại ngay cả khi không có trường hợp ngoại lệ nào thực sự bị ném. Tất cả các khối try/catch này có yêu cầu thiết lập bổ sung và xé rách trên mỗi lời gọi hàm hay không bằng cách nào đó được xử lý tại thời gian biên dịch? Hoặc có những vấn đề khác mà tôi đã không xem xét?

Trả lời

21

Tôi nghĩ đây thực sự là một ý tưởng tồi.

Tính di động là một mục tiêu rất xứng đáng, nhưng không phải khi nó dẫn đến một giải pháp có tính xâm nhập, hiệu suất phá hoại và thực hiện kém hơn.

Mọi nền tảng (Windows/Linux/PS2/iPhone/etc) Tôi đã làm việc trên đã cung cấp một cách để đi bộ ngăn xếp khi một ngoại lệ xảy ra và kết hợp địa chỉ với tên hàm. Có, không cái nào trong số này là di động nhưng khung báo cáo có thể và thường mất ít hơn một hoặc hai ngày để viết một phiên bản mã đi bộ ngăn xếp theo nền tảng cụ thể.

Không chỉ là thời gian này ít hơn nó sẽ tạo/duy trì một giải pháp đa nền tảng, nhưng kết quả là tốt hơn nhiều;

  • Không cần phải sửa đổi chức năng
  • Bẫy treo trong thư viện của bên tiêu chuẩn hoặc thứ ba
  • Không cần cho một try/catch trong mọi chức năng (chậm và bộ nhớ chuyên sâu)
2

Tôi không nghĩ rằng có một nền tảng "độc lập" cách để làm điều này - sau khi tất cả, nếu có, sẽ không có một nhu cầu cho StackWalk hoặc đặc biệt gcc stack truy tìm tính năng bạn đề cập đến.

Sẽ hơi lộn xộn, nhưng cách tôi sẽ thực hiện điều này là tạo một lớp cung cấp giao diện nhất quán để truy cập theo dõi ngăn xếp, sau đó có #ifdefs trong triển khai sử dụng các phương pháp nền tảng cụ thể thích hợp để thực sự đặt dấu vết ngăn xếp lại với nhau.

Bằng cách đó, việc sử dụng lớp của bạn là nền tảng độc lập và chỉ cần sửa đổi lớp đó nếu bạn muốn nhắm mục tiêu một số nền tảng khác.

0

Đây sẽ là chậm hơn nhưng có vẻ như nó sẽ hoạt động.

Từ những gì tôi hiểu được vấn đề trong việc thực hiện nhanh chóng, di động, theo dõi ngăn xếp là việc thực hiện ngăn xếp là cả hệ điều hành và CPU cụ thể, do đó, nó ngầm là một vấn đề nền tảng cụ thể. Một cách khác là sử dụng các hàm MS/glibc và sử dụng #ifdef và bộ tiền xử lý thích hợp xác định (ví dụ: _WIN32) để triển khai các giải pháp cụ thể nền tảng trong các bản dựng khác nhau.

0

Vì việc sử dụng ngăn xếp là nền tảng cao và phụ thuộc vào triển khai thực hiện, không có cách nào để thực hiện trực tiếp điều đó hoàn toàn di động. Tuy nhiên, bạn có thể xây dựng một giao diện di động cho một nền tảng và triển khai cụ thể của trình biên dịch, bản địa hóa các vấn đề càng nhiều càng tốt. IMHO, đây sẽ là cách tiếp cận tốt nhất của bạn.

Việc triển khai trình theo dõi sau đó sẽ liên kết với bất kỳ thư viện trợ giúp cụ thể nền tảng nào có sẵn. Sau đó nó sẽ hoạt động chỉ khi một ngoại lệ xảy ra, và thậm chí sau đó chỉ khi bạn gọi nó từ một khối catch. API tối thiểu của nó sẽ đơn giản trả về một chuỗi chứa toàn bộ dấu vết.

Yêu cầu bộ mã hóa để tiêm bắt và xử lý lại trong chuỗi cuộc gọi có chi phí thời gian chạy đáng kể trên một số nền tảng và áp đặt chi phí bảo trì lớn trong tương lai.

Điều đó nói rằng, nếu bạn chọn sử dụng cơ chế nắm bắt/ném, đừng quên rằng ngay cả C++ vẫn có bộ tiền xử lý C, và các macro __FILE____LINE__ được xác định. Bạn có thể sử dụng chúng để bao gồm tên tệp nguồn và số dòng trong thông tin theo dõi của bạn.

6

Tra cứu Nested Diagnostic Context một lần. Dưới đây là một gợi ý nhỏ:

class NDC { 
public: 
    static NDC* getContextForCurrentThread(); 
    int addEntry(char const* file, unsigned lineNo); 
    void removeEntry(int key); 
    void dump(std::ostream& os); 
    void clear(); 
}; 

class Scope { 
public: 
    Scope(char const *file, unsigned lineNo) { 
     NDC *ctx = NDC::getContextForCurrentThread(); 
     myKey = ctx->addEntry(file,lineNo); 
    } 
    ~Scope() { 
     if (!std::uncaught_exception()) { 
      NDC *ctx = NDC::getContextForCurrentThread(); 
      ctx->removeEntry(myKey); 
     } 
    } 
private: 
    int myKey; 
}; 
#define DECLARE_NDC() Scope s__(__FILE__,__LINE__) 

void f() { 
    DECLARE_NDC(); // always declare the scope 
    // only use try/catch when you want to handle an exception 
    // and dump the stack 
    try { 
     // do stuff in here 
    } catch (...) { 
     NDC* ctx = NDC::getContextForCurrentThread(); 
     ctx->dump(std::cerr); 
     ctx->clear(); 
    } 
} 

Chi phí cho việc thực hiện NDC. Tôi đã chơi với một phiên bản đánh giá lười biếng cũng như một trong đó chỉ giữ một số cố định của các mục là tốt. Điểm mấu chốt là nếu bạn sử dụng các hàm tạo và hàm hủy để xử lý ngăn xếp sao cho bạn không cần tất cả những khối khó chịu đó là các khối try/catch và thao tác rõ ràng ở mọi nơi.

Nhức đầu cụ thể theo nền tảng duy nhất là phương pháp getContextForCurrentThread(). Bạn có thể sử dụng một triển khai nền tảng cụ thể bằng cách sử dụng lưu trữ cục bộ luồng để xử lý công việc nhiều nhất nếu không phải tất cả các trường hợp.

Nếu bạn đang thực hiện nhiều hơn theo định hướng và sống trong thế giới của các file log, sau đó thay đổi phạm vi để giữ một con trỏ đến tên tập tin và dòng số và bỏ qua điều NDC hoàn toàn:

class Scope { 
public: 
    Scope(char const* f, unsigned l): fileName(f), lineNo(l) {} 
    ~Scope() { 
     if (std::uncaught_exception()) { 
      log_error("%s(%u): stack unwind due to exception\n", 
         fileName, lineNo); 
     } 
    } 
private: 
    char const* fileName; 
    unsigned lineNo; 
}; 

chí này cung cấp cho bạn một dấu vết ngăn xếp tốt đẹp trong tệp nhật ký của bạn khi một ngoại lệ được ném.Không cần bất kỳ ngăn xếp thực đi bộ, chỉ là một chút thông điệp log khi một ngoại lệ được ném;)

+1

Kiểm tra http://www.gotw.ca/gotw/047.htm của Herb Sutter đối với một góc nhìn khác. – AJG85

1

Trong debugger:

Để có được stack trace về nơi một ngoại lệ là ném từ Tôi chỉ stcik giờ nghỉ điểm trong std :: constructor ngoại lệ.

Do đó, khi ngoại lệ được tạo, trình gỡ rối sẽ dừng và sau đó bạn có thể thấy dấu vết ngăn xếp tại điểm đó. Không hoàn hảo nhưng nó hoạt động phần lớn thời gian.

+1

Anh ấy không yêu cầu hướng dẫn trình gỡ lỗi. –

+1

Và điều này không giúp đỡ trong việc làm phiền? –

+0

+1 để biết mẹo hữu ích – Mawg

1

Quản lý ngăn xếp là một trong những điều đơn giản phức tạp rất nhanh. Tốt hơn nên để nó cho các thư viện chuyên ngành. Bạn đã thử libunwind chưa? Công trình tuyệt vời và AFAIK nó di động, mặc dù tôi chưa bao giờ thử nó trên Windows.

Các vấn đề liên quan