[Học lập trình C++] Chương 1: 1.11 – Debugging chương trình của bạn
1.11 – Debugging chương trình của bạn
Lỗi cú pháp và lỗi ngữ nghĩa
Chương trình có thể
khó khăn, và có nhiều cách để mắc sai lầm. Lỗi tạo ra rơi vào một trong hai trường
hợp: Lỗi cú pháp và lỗi ngữ nghĩa.
Một lỗi cú pháp xuất
hiện khi bạn viết một câu lệnh mà không hợp lệ theo ngữ pháp của ngôn ngữ C++.
Điều này bao gồm các lỗi như quên dấu chấm phẩy, không khai báo biến, thiếu dấu
ngoặc móc, hoặc ngoặc tròn, và không kết thúc chuỗi. Ví dụ, chương trình sau chứa
đựng một vài lỗi cú pháp:
#include <iostream>; // preprocessor
statements can't have a semicolon on the end
int main()
{
std:cout < "Hi
there; << x; // invalid operator (:), unterminated string (missing
"), and undeclared variable
return 0 // missing semicolon at end of
statement
}
May mắn thay, trình
biên dịch sẽ bắt được lỗi cú pháp và tạo ra một cảnh báo hay lỗi, vì vậy bạn sẽ
dễ dàng xác định và sửa lỗi vấn đề. Sau đó chỉ cần biên dịch lại cho đến khi
tìm ra được tất cả các lỗi.
Một khi chương trình
của bạn biên dịch một cách đúng đắn, kết quả thu được vẫn có thể không đáng tin
cậy. Một lỗi ngữ nghĩa xuất hiện khi một câu lệnh đúng cú pháp, nhưng không thực
hiện những gì người lập trình mong đợi.
Thỉnh thoảng những lỗi
này sẽ làm cho chương trình của bạn bị ngừng, giống như trường hợp chia cho số
0:
#include <iostream>
int main()
{
int a = 10;
int b = 0;
std::cout << a
<< " / " << b << " = " << a / b; //
division by 0 is undefined
return 0;
}
Thỉnh thoảng những
trường hợp tạo ra giá trị sai:
1
2
3
4
5
6
7
|
#include <iostream>
int main()
{
std::cout << "Hello,
word!"; // spelling error
return 0;
}
|
or
1
2
3
4
5
6
7
8
9
10
11
12
|
#include <iostream>
int add(int x, int y)
{
return x - y; // function is supposed to
add, but it doesn't
}
int main()
{
std::cout << add(5, 3); // should
produce 8, but produces 2
return 0;
}
|
Thật không may, trình
biên dịch không thể bắt được những lỗi dạng này, bởi vì trình biên dịch chỉ biết
những gì bạn viết, không biết những gì bạn mong muốn.
Trong ví dụ phía trên,
lỗi dễ dàng phát hiện. Nhưng trong những chương trình không bình thường thì nhiều
lỗi ngữ nghĩa sẽ không dễ dàng tìm ra bằng mắt thường.
May mắn thay, trình gỡ
rối sẽ giải quyết vấn đề đó.
Trình gỡ rối
Một trình gỡ rối là một
chương trình máy tính mà cho phép người lập trình điều khiển chương trình thực
thi và quan sát chuyện gì xảy khi trong khi chạy chương trình. Ví dụ, lập trình
viên có thể dùng trình gỡ rối để thực thi chương trình theo từng dòng, kiểm tra
các giá trị của các biến trong quá trình chạy. Bằng cách so sánh giá trị của biến
thật với giá trị mong đợi, hoặc xem thứ tự thực thi trong code, có thể giúp rất
nhiều trong việc phát hiện lỗi ngữ nghĩa.
Trình gỡ rối sớm nhất,
giống như gdb, có một giao diện dòng lệnh nơi mà lập trình viên phải gõ lệnh trực
tiếp để làm chúng hoạt động. Trình gỡ rối sau này (giống như trình gỡ rối tubo
của Borland’s) mang đến một giao diện để hoạt động với nó một cách dễ dàng. Hầu
hết những IDE có sẵn ngày nay đều có trình gỡ rối tích hợp – Đó là trình gỡ rối
được xây dựng cùng với trình soạn thảo, vì vậy bạn có thể gỡ rối sử dụng môi
trường giống như bạn dùng để viết chương trình của bạn (không cần phải chuyển đổi
chương trình).
Tất cả trình gỡ rối
hiện nay chứa đựng những tính năng chuẩn và cơ bản như nhau. Tuy nhiên, có một ít
sự khác nhau về cách sắp xếp các menu, và phím tắt.
Stepping
Stepping là một tính
năng của trình gỡ rối để thực thi từng dòng lệnh trong chương trình của bạn. Điều
này cho phép bạn xem xét từng dòng code trong sự cô lập để xác định xem nó có
hoạt động giống như mong muốn.
Thực ra có 3 lệnh
stepping khác nhau: Step into, step over, and step out. Chúng ta sẽ tìm hiểu từng
cái một.
Step into
Lệnh step into thực
thi dòng lệnh kế tiếp. Nếu dòng này là một lời gọi hàm, step into đi vào hàm và
trả điều khiển vào đỉnh của hàm.
Hãy quan sát một ví dụ
đơn giản:
2
3
4
5
6
7
8
9
10
11
12
|
#include <iostream>
void printValue(int nValue)
{
std::cout << nValue;
}
int main()
{
printValue(5);
return 0;
}
|
#include <iostream>
void printValue(int nValue)
{
std::cout << nValue;
}
int main()
{
printValue(5);
return 0;
}
Như bạn đã biết, khi
chạy chương trình, thực thi sẽ bắt đầu với việc gọi hàm main(). Bởi vì chúng ta
muốn gỡ rối bên trong hàm main(), hãy bắt đầu sử dụng lệnh step into.
Trong Visual Studio
2005 Express, vào menu debug và chọn Step into hoặc nhấn F11.
Nếu bạn đang sử dụng
một IDE, tìm lệnh Step into trong menu và chọn nó. Khi bạn làm điều này, có hai
thứ xảy ra. Đầu tiên, bởi vì ứng dụng của chúng ta là một chương trình hiển thị
trên màn hình, một của sổ đầu ra console sẽ mở ra. Nó sẽ trống trơn bởi vì
chúng ta chưa xuất ra gì cả. Thứ hai, bạn sẽ nhìn thấy một vài kiểu đánh dấu xuất
hiện bên trái của hàm main(). Trong Visual 2005 Express, sự đánh dấu này là
hàng màu vàng. Nếu bạn đang sử dụng một IDE khác, bạn sẽ nhìn thấy một cái gì
đó phục vụ mục đích tương tự.
Đánh dấu hàng này cho
thấy rằng dòng đang được trỏ sẽ được thực thi kế tiếp. Trong trường hợp này trình
gỡ rối đang nói cho chúng ta rằng dòng kế tiếp sẽ được thực thi trong dấu ngoặc
móc.
Điều này nghĩa là
dòng kế tiếp sẽ được thực thi là gọi hàm printValue(). Chọn “Step into” lần nữa.
Bởi vì hàm printValue là một lời gọi hàm, chúng ta “Step into” hàm, và mũi tên sẽ
nhảy lên đỉnh của hàm printValue().
Chọn “Step into” Để
thực thi dấu ngoặc nhọn mở của hàm printValue(). Tại điểm này, hàng sẽ chỉ đến
std::cout << nValue;
Chọn “Step over” lần
này (đây sẽ thực thi dòng code này mà không thực thi toán tử <<). Bởi vì
câu lệnh cout đã được thực thi, bạn sẽ nhìn thấy giá trị 5 xuất hiện trong của
sổ ngõ ra.
Chọn “Step into” lần
nữa để thực thi dấu ngoặc nhọn đóng của printValue(). Tại điểm này printValue()
đã thực hiện xong và quyền điều khiển được trả vè cho hàm main().
Bạn sẽ chú ý rằng mũi
tên lại trỏ tới printValue()!
Trong khi bạn có thể
nghĩ rằng trình gỡ rối định gọi là hàm printValue(), trong thực tế trình gỡ rối
chỉ cho bạn biết rằng nó đang trở về từ lời gọi hàm.
Chọn “Step into” hai
lần nữa. Tới điểm này, chúng đã thực thi tất cả các dòng lệnh của chương trình,
vì thế chúng ta đã hoàn thành. Một vài trình gỡ rối sẽ kết thúc phần debugging
một các tự động. Visual Studio thì không, vì vậy nếu bạn đang sử dụng Visual
Studio, chọn “Stop debugging” từ debug menu. Đây kết thúc quá trình debugging.
Chú ý rằng “Stop debugging”
có thể được sử dụng tại bất kì điểm nào trong quá trình debugging để kết thúc
phần debugging.
Step over
Giống như “Step into”,
lệnh “Step over” thực thi dòng lệnh tiếp theo của code. Nếu dòng lệnh là một lời
gọi hàm, “Step over” thực thi tất cả code trong hàm và tra điều khiển đến bạn
sau khi hàm đã được thực thi.
Chú ý đối với
Code::Blocks, “Step over” được gọi là “Next line”.
Hãy xem một ví dụ của
việc sử dụng này trong ví dụ giống như chương trình ở trên:
1
2
3
4
5
6
7
8
9
10
11
12
|
#include <iostream>
void printValue(int nValue)
{
std::cout << nValue;
}
int main()
{
printValue(5);
return 0;
}
|
“Step into” chương
trình cho đến khi câu lệnh tiếp theo được thực thi là gọi hàm printValue().
Thay vì thực hiện từng
bước trong hàm printValue(), chọn “Step over” thay thế. Trình gỡ rối sẽ thực
thi hàm (in ra giá trị 5 trong của sổ đầu ra) và sau đó trả điều khiển về cho
dòng kế tiếp (return 0;)
Step over cung cấp một
cách tiện lợi để bỏ qua hàm khi bạn đã chắc chắn rằng nó hoạt động hoặc không cần
phải debug.
Step out
Không giống như hai lệnh
stepping, “Step out” không chỉ thực thi dòng kế tiếp của code. Thay vào đó, nó
thực thi tất cả các code còn lại trong hàm hiện tại của bạn, và trả điều khiển
cho bạn khi hàm kết thúc.
Hãy quan sát một ví dụ
của việc sử dụng trong nó trong chương trình sau đây.
1
2
3
4
5
6
7
8
9
10
11
12
|
#include <iostream>
void printValue(int nValue)
{
std::cout << nValue;
}
int main()
{
printValue(5);
return 0;
}
|
“Step into” chương
trình cho đến khi ở bên trong hàm prinValue().
Sau đó chọn “Step out”.
Bạn sẽ chú ý giá trị 5 xuất hiện trên ngõ ra, và trình gỡ rối trả điều khiển
cho bạn sau khi hàm kết thúc.
Run to cursor
Trong khi stepping hữu
ích cho việc kiểm tra từng dòng code đơn, trong một chương trình lớn, nó có thể
mất thời gian để chạy từng bước như vậy, bạn chỉ cần trỏ vào nơi mà bạn muốn kiểm
tra chi tiết.
May mắn là các trình
gỡ rối hiện nay cung cấp một vài công cụ để giúp chúng ta debug chương trình một
cách hiệu quả.
Lệnh hữu ích đầu tiên
thường được gọi là Run to cursor. Lệnh này thực thi chương trình giống như bình
thường cho đến khi nó tới được dòng code mà bạn chọn. Sau đó nó trả điểu khiển
về cho bạn để bạn có thể debug bắt đầu từ điểm đó.
1
2
3
4
5
6
7
8
9
10
11
12
|
#include <iostream>
void printValue(int nValue)
{
std::cout << nValue;
}
int main()
{
printValue(5);
return 0;
}
|
Đơn giản đặt con trỏ
của bạn tại dòng std::cout << nValue; bên trong hàm printValue(), sau đó
phải chuột và chọn “Run to cursor.
Bạn
sẽ chú ý rằng mũi tên chỉ đến dòng sẽ được thực thi kế tiếp dòng mà bạn chọn.
Chương trình của bạn được thực thi đến điểm này và hiện giờ đang đợi cho lệnh
debugging tiếp theo.
Run
Một
khi bạn đang trong quá trình debuging một chương trình, bạn có thể nói với
trình gỡ rối chạy cho đến hết chương trình (hoặc breakpoint kế tiếp, cái mà
chúng ta sẽ thảo luận tiếp theo đây). Trong Visual Studio 2005 Express, lệnh
này được gọi là Continue. Trong trình gỡ rối khác, nó có thể được gọi là “Run”
hoặc “Go”.
Nếu
bạn đã cùng theo dõi suốt ví dụ này, bạn sẽ ở trong hàm printValue(). Chọn lệnh
run, và chương trình của bạn sẽ hoàn thành thực thi và sau đó kết thúc.
Breakpoints
Chủ
đề cuối cùng chúng ta sẽ nó về breakpoint. Một breakpoint là một điểm đánh dấu
đặc biệt nói với trình gỡ rối đặc biệt để ngừng việc thực thi của chương trình
tại điểm breakpoint khi chạy trong chế độ debug.
Để
thiết lập mọt breakpoint trong Visual Studio 2005 Express, tìm đến menu Debug
và chọn “Toggle Breakpoint” (bạn có thể phải chuột, chọn breakpoint ->
Insert Breakpoint). Bạn sẽ nhìn thấy một biểu tượng mới xuất hiện:
Tiếp
tục và đặt một breakpoint trên dòng std::cout << nValue;
Bây
giờ, chọn “Step into” để bắt đầu phần debugging, và sau đó “Continue” để trình
gỡ rối chạy code của bạn, và hãy quan sát breakpoint hoạt động ra sao. Bạn sẽ
chú ý rằng thay vì chạy tất cả mọi cách để kết thúc chương trình, trình gỡ rối
đã ngừng tại breakpoint!
Breakpoints
là một công cụ cực kì hữu ích nếu bạn muốn kiểm tra một phần đặc biệt của code.
Đơn giản đặt một breakpoint tại định của phần code đó, thông báo với trình gỡ rối
chạy, và trình gỡ rối sẽ tự động ngừng lại mỗi lần nó gặp phải breakpoint. Sau
đó bạn có thể dùng lệnh stepping tại đó để xem chương trình chạy từng dòng một.
Một
chý ý cuối cùng: Cho đến bây giờ, chúng ta đã sử dụng “Step into” để bắt đầu phần
debugging. Tuy nhiên, nó có thể nói với trình gỡ rối chỉ chạy từ đầu cho tới kết
thúc chương trình ngay lập tức. Trong Visual Studio 2005 Express, điều này được
thực hiện bằng cách chọn “Start debugging” từ Debug menu. Trình gỡ rối khác sẽ
có lệnh tương tự. Khi bạn sử dụng trong liên kết với breakpoints, có thể giảm
thiểu số lượng lệnh bạn cần dùng để có được mục tiêu chi tiết hơn việc sử dụng lệnh
stepping.
Kết luận
Chúc
mừng bạn, bây giờ bạn đã biết những cách chính để cho trình gỡ rối thực hiện
trên code của bạn. Tuy nhiên, đây chỉ là một nửa những lợi ích của debugger mà
thôi. Trong bài học tiếp theo sẽ nói về làm thế nào để kiểm tra giá trị của các
biến mà chúng ta đang debuging, cũng như một vài cửa sổ của thông tin thêm vào chúng
ta có thể sử dụng để giúp debug code của chúng ta
Nhận xét
Đăng nhận xét