
Simple try/catch
- C++ có exceptions, C thì không. Chúng ta có thể dùng
throwđể quăng một loại exception vàcatchđể bắt loại exception đó.
- Đổi với các kiểu cơ bản (
int,char,bool,double, enum) chúng ta có thể bắt bằng giá trị.
- Đối với các kiểu lớn hoặc object, chúng ta nên dùng “
<strong>const T&</strong>” để tăng performance, “&” để reference tránh copy object mới, “const” để read-only
#include <iostream>
using namespace std;
int main () {
try
{
throw 404;
}
catch (int errorCode)
{
cout << "ErrorCode: " << errorCode << endl;
}
cout << "done" << endl;
return 0;
}
ErrorCode: 20
done#include <iostream>
using namespace std;
int main () {
try
{
throw "bad data";
}
catch (const char* msg)
{
cout << "ErrorMessage: " << msg << endl;
}
cout << "done" << endl;
return 0;
}
ErrorMessage: bad data
done#include <iostream>
using namespace std;
int main () {
try
{
throw string("bad data");
}
catch (const string& msg)
{
cout << "ErrorMessage: " << msg << endl;
}
cout << "done" << endl;
return 0;
}
ErrorMessage: bad data
done#include <iostream>
using namespace std;
int main()
{
try
{
throw runtime_error("File not found");
}
catch (exception& ex)
{
cout << "EXCEPTION: " << ex.what() << endl;
}
cout << "Done" << endl;
return 0;
}
ErrorMessage: File not found
DoneMultiple try/catch
Chúng ta có thể dùng nhiều khối catch để bắt các exception có kiểu khác nhau.
#include <iostream>
using namespace std;
int main() {
try
{
// code here
throw invalid_argument("bad input");
}
catch (int ex) { cout << "catch int exception:" << ex << endl; }
catch (char ex) { cout << "catch char exception:" << ex << endl; }
catch (const char* ex) { cout << "catch string exception:" << ex << endl; }
catch (const string& msg) { cout << "catch string exception:" << ex << endl; }
catch (const runtime_error& ex) { cout << "catch std::runtime_error:" << ex.what() << endl; }
catch (const logic_error& ex) { cout << "catch std::logic_error:" << ex.what() << endl; }
catch (const invalid_argument& ex) { cout << "catch std::invalid_argument:" << ex.what() << endl; }
catch (const exception& ex) { cout << "catch generic std::exception:" << ex.what() << endl; }
catch (...) { cout << "catch remaining unknown exception" << endl; }
}
catch std::invalid_argument:bad inputNested try/catch
Chúng ta có thể dùng throw; bên trong khối catch để rethrow exception hiện tại hoặc throw msg; nó sẽ copy và tạo new throw của object đó
try {
try {
// code here
}
catch (const string& msg) {
// throw msg;
throw;
}
}
catch (...) {
cout << "Exception occurred";
}
Throw/catch không phải là thao tác rẻ: nó liên quan đến việc tháo gỡ stack (stack unwinding), gọi destructor cho các đối tượng trong phạm vi, và nhảy đến khối catch
Việc ném lại một ngoại lệ khác chỉ thêm chi phí tạo đối tượng ngoại lệ mới và thực hiện lại quá trình ném.
| Trường hợp | Chi phí chính |
| throw; (ném lại ngoại lệ gốc) | Không tạo đối tượng mới, chỉ tiếp tục quá trình unwinding. |
| throw NewException(...) (ném ngoại lệ khác) | Tạo đối tượng ngoại lệ mới + unwinding. |
Tự tạo exeception
Tạo class BaseException (có thể kế thừa từ std::exception hoặc không), và class RuntimeException : public BaseException
Hàm GetStackTrace() để lấy stack trace
#include <execinfo.h>để dùng các func backtrace(), backtrace_symbols_fd()#include <unistd.h>để dùngabi::__cxa_demangle,pipe,...abi::__cxa_demangledùng để dịch các tên hàm đã bị mã hóa (mangled name) của C++ (thường thấy khi lấy stack trace) về dạng tên hàm gốc mà con người đọc được.pipe()dùng để tạo kênh giao tiếp giữa các tiến trình (inter-process communication)- Compiler thường tạo tên hàm dạng "_ZN16RuntimeException4wha", func "Demangle(&stack_trace);" sẽ chuyển thành "RuntimeException::what()" giúp dễ đọc.
Hàm
backtrace_symbols_fd()(hoặcbacktrace_symbols()) trong thư việnexecinfo.hchỉ có khả năng hiển thị tên file thực thi và địa chỉ bộ nhớ (như [0x40335e]), chứ bản thân nó không thể tự dịch các địa chỉ đó thành tên hàm cụ thể hay số dòng (line number) trong file code [.cpp].Để hiển thị được số dòng chính xác, bạn cần lấy các địa chỉ bộ nhớ đó và dịch chúng thông qua công cụ
addr2linecủa Linux ( cần compile với flag -g để file thực thi ./btest có các thông tin debug cần thiết)vd: addr2line -e ./btest 0x40335e 0x403583 0x402633
#ifndef _COMMON_H_
#define _COMMON_H_
#define AT __FILE__, __LINE__, __PRETTY_FUNCTION__
// shorthand of "throw RuntimeException("This is a dummy exception", AT);"
#define THROW_RUNTIME_ERROR(msg) throw RuntimeException(msg, __FILE__, __LINE__, __PRETTY_FUNCTION__)
#define DUMP_ERROR_TO_LOG(ex) \
{ \
vector<string> text = ex.message(); \
cout << "----------------------------------" << endl; \
for(int i = 0; i < text.size(); i++) \
cout << text[i] << endl; \
cout << "----------------------------------" << endl; \
}
#include <iostream>
#include <string>
#include <vector>
#include <sstream>
#include <cxxabi.h> // for using "abi::__cxa_demangle"
#include <unistd.h> // for using "pipe"
using std::cout;
using std::endl;
using std::string;
using std::to_string;
using std::stringstream;
using std::vector;
extern "C" {
#include <execinfo.h> // for using backtrace(), backtrace_symbols_fd()
}
class BaseException
{
public:
int m_code; // exception code
string m_what; // exception message
string m_where; // file and function position
string m_type; // exception type
vector<string> m_stack_trace; // detail stack trace
/* get exception message */
virtual const string what() = 0;
/* get error detail */
virtual vector<string> message() = 0;
protected:
/* default constructor */
BaseException(const char* type, const char* what, const char* file, int line, const char* func = "", int code = 0);
/* default virtual destructor */
virtual ~BaseException();
};
class RuntimeException : public BaseException
{
public:
/* default constructor */
RuntimeException(const char* what, const char* file, int line, const char* func = "", int code = 0);
RuntimeException(string what, const char* file, int line, const char* func = "", int code = 0);
/* default virtual destructor */
~RuntimeException();
const string what();
vector<string> message();
};
#endif // _COMMON_H_#include "common.h"
// Giữ nguyên hàm SplitIntoLines của bạn
void SplitIntoLines(string src,
vector<string>* dest,
const string& replace = "",
const string& with = "")
{
stringstream ss(src);
string line;
while(getline(ss, line, '\n')) {
if (line.empty()) continue;
if(replace == "")
dest->push_back(line);
else
{
size_t start = line.find(replace);
if(start != string::npos)
line.replace(start, replace.size(), with);
dest->push_back(line);
}
}
}
// Hàm Demangle mới: Tự động phân tích chuỗi và dùng addr2line để lấy file + line number
void Demangle(vector<string>* lines)
{
// Lấy đường dẫn file thực thi hiện tại
char exepath[1024] = {0};
ssize_t len = readlink("/proc/self/exe", exepath, sizeof(exepath) - 1);
if (len == -1) return;
for(size_t i = 0; i < lines->size(); i++)
{
string& line = (*lines)[i];
// 1. Tìm địa chỉ hex bên trong cặp dấu ngoặc vuông `[...]` (Ví dụ: [0x40335e])
size_t open_bracket = line.find('[');
size_t close_bracket = line.find(']', open_bracket);
if (open_bracket != string::npos && close_bracket != string::npos)
{
string addr = line.substr(open_bracket + 1, close_bracket - open_bracket - 1);
// 2. Gọi addr2line để dịch địa chỉ bộ nhớ thành tên hàm và số dòng
char cmd[512];
snprintf(cmd, sizeof(cmd), "addr2line -f -C -e %s %s", exepath, addr.c_str());
FILE* fp = popen(cmd, "r");
if (fp) {
char func_buf[256] = {0};
char file_buf[512] = {0};
// addr2line trả về 2 dòng: dòng 1 là tên hàm (đã demangle nhờ flag -C), dòng 2 là file:line
if (fgets(func_buf, sizeof(func_buf), fp) && fgets(file_buf, sizeof(file_buf), fp)) {
string func(func_buf);
string file(file_buf);
// Xóa ký tự xuống dòng \n
if (!func.empty() && func.back() == '\n') func.pop_back();
if (!file.empty() && file.back() == '\n') file.pop_back();
// Nếu dịch thành công (không bị trả về kết quả ẩn danh ??)
if (file.find("??") == string::npos) {
line = func + " tại " + file;
}
}
pclose(fp);
}
}
}
}
vector<string> GetStackTrace()
{
vector<string> stack_trace;
int fd[2];
// Cần tạo pipe trước khi ghi dữ liệu
if (pipe(fd) == -1) return stack_trace;
// Khối lấy backtrace cũ của bạn giữ nguyên
{
static void *(ErrBuf[8192]);
int bt_size;
bt_size = backtrace(ErrBuf, 8192);
backtrace_symbols_fd(ErrBuf, bt_size, fd[1]);
}
// Đóng đầu ghi lại để hàm read() phía dưới biết khi nào luồng dữ liệu kết thúc (EOF)
close(fd[1]);
stringstream ss;
char buff[8192];
int bytes = 0;
// Đọc toàn bộ dữ liệu từ pipe
while((bytes = read(fd[0], buff, sizeof(buff) - 1)) > 0)
{
buff[bytes] = '\0';
ss << buff;
}
close(fd[0]); // Đóng đầu đọc sau khi hoàn thành
// Lấy thư mục gốc từ biến môi trường (nếu có)
string imx_src_dir;
if(getenv("IMX_HOME") != NULL)
imx_src_dir = string(getenv("IMX_HOME")) + "/src/";
SplitIntoLines(ss.str(), &stack_trace, imx_src_dir, "$S/");
Demangle(&stack_trace);
// Loại bỏ dòng đầu tiên (chính là hàm GetStackTrace) để stack sạch hơn
if (!stack_trace.empty()) {
stack_trace.erase(stack_trace.begin());
}
return stack_trace;
}
/* BaseException */
BaseException::BaseException( const char* type
, const char* what
, const char* file
, int line
, const char* func
, int code ) :
m_type(type),
m_what(what),
m_where(string(file) + ":" + to_string(line) + " in " + string(func)),
m_code(code)
{
m_stack_trace = GetStackTrace();
};
BaseException::~BaseException()
{};
/* RuntimeException */
RuntimeException::RuntimeException( const char* what
, const char* file
, int line
, const char* func
, int code ) :
BaseException("Runtime", what, file, line, func, code)
{
};
RuntimeException::RuntimeException( string what
, const char* file
, int line
, const char* func
, int code ) :
BaseException("Runtime", what.c_str(), file, line, func, code)
{
};
RuntimeException::~RuntimeException()
{
};
const string RuntimeException::what()
{
return "Runtime exception: " + m_what + " in: " + m_where;
};
vector<string> RuntimeException::message()
{
vector<string> s;
s.push_back("EXCEPTION: Runtime");
s.push_back(" WHAT: " + m_what);
s.push_back(" WHERE: " + m_where);
for(int i = 0; i < m_stack_trace.size(); i++)
s.push_back((i == 0 ? " STACK: " : " : ") + m_stack_trace[i]);
return s;
};// g++ btest.cpp common.cpp -o btest
#include "common.h"
int main() {
try
{
throw RuntimeException("This is a dummy exception", AT);
} catch (RuntimeException& ex) {
cout << ex.what() << endl;
DUMP_ERROR_TO_LOG(ex);
}
return 0;
}Compile với "-g" (enable debug symbols) để thấy function names và line numbers.
[btnguyen@hpserver btest]$ g++ btest.cpp common.cpp -o btest
[btnguyen@hpserver btest]$ ./btest
Runtime exception: This is a dummy exception in: btest.cpp:6 in int main()
----------------------------------
EXCEPTION: Runtime
WHAT: This is a dummy exception
WHERE: btest.cpp:6 in int main()
STACK: ./btest[0x40392a]
: ./btest[0x403b4f]
: ./btest[0x402943]
: /lib64/libc.so.6(__libc_start_main+0xe5)[0x7f441f786865]
: ./btest[0x40284e]
----------------------------------
[btnguyen@hpserver btest]$[btnguyen@hpserver btest]$ g++ -g btest.cpp common.cpp -o btest
[btnguyen@hpserver btest]$ ./btest
Runtime exception: This is a dummy exception in: btest.cpp:6 in int main()
----------------------------------
EXCEPTION: Runtime
WHAT: This is a dummy exception
WHERE: btest.cpp:6 in int main()
STACK: BaseException::BaseException(char const*, char const*, char const*, int, char const*, int) tại /home/btnguyen/Code/btest/common.cpp:153 (discriminator 1)
: RuntimeException::RuntimeException(char const*, char const*, int, char const*, int) tại /home/btnguyen/Code/btest/common.cpp:167
: main tại /home/btnguyen/Code/btest/btest.cpp:6 (discriminator 2)
: /lib64/libc.so.6(__libc_start_main+0xe5)[0x7ff34e0a1865]
: ./btest[0x40284e]
----------------------------------
[btnguyen@hpserver btest]$Khi biên dịch, bạn nên thêm flag
-rdynamiccùng với-g. Flag này sẽ ép trình biên dịch export bảng ký tự (symbol table) vào file thực thi, giúpbacktrace_symbols_fdsinh ra log ở Dạng 1 (có sẵn tên hàm để demangle tốc độ cao).bash
g++ -g -rdynamic btest.cpp common.cpp -o btest