Tại sao printf () xấu khi gỡ lỗi hệ thống nhúng?


16

Tôi đoán đó là một điều xấu khi cố gắng gỡ lỗi một dự án dựa trên vi điều khiển bằng cách sử dụng printf().

Tôi có thể hiểu rằng bạn không có nơi nào được xác định trước để xuất ra và nó có thể tiêu thụ các chân có giá trị. Đồng thời tôi đã thấy mọi người sử dụng pin UART TX để xuất ra thiết bị đầu cuối IDE với DEBUG_PRINT()macro tùy chỉnh .


12
Ai nói với bạn rằng nó là xấu? "Thường không phải là tốt nhất" không giống như "xấu" không đủ tiêu chuẩn.
Spehro Pefhany

6
Tất cả điều này nói về việc có bao nhiêu chi phí, nếu tất cả những gì bạn cần làm là xuất ra các thông báo "Tôi ở đây", bạn hoàn toàn không cần printf, chỉ là một thói quen để gửi một chuỗi đến UART. Điều đó, cộng với mã để khởi tạo UART có thể dưới 100 byte mã. Thêm khả năng xuất một vài giá trị hex sẽ không làm tăng thêm nhiều.
tcrosley

7
@ChetanBhargava - Các tệp tiêu đề C thường không thêm mã vào tệp thực thi. Chúng chứa các tờ khai; nếu phần còn lại của mã không sử dụng những thứ được khai báo, mã cho những thứ đó không được liên kết. printfTất nhiên, nếu bạn sử dụng , tất cả mã cần thực hiện printfsẽ được liên kết với tệp thực thi. Nhưng đó là vì mã đã sử dụng nó, không phải vì tiêu đề.
Pete Becker

2
@ChetanBhargava Bạn thậm chí không cần bao gồm <stdio.h> nếu bạn cuộn thói quen đơn giản của riêng mình để xuất một chuỗi như tôi đã mô tả (xuất các ký tự sang UART cho đến khi bạn thấy '\ 0') '
tcrosley

2
@tcrosley Tôi nghĩ rằng lời khuyên này có thể phù hợp nếu bạn có một trình biên dịch hiện đại tốt, nếu bạn sử dụng printf trong trường hợp đơn giản không có chuỗi định dạng gcc và hầu hết những người khác thay thế nó bằng một cuộc gọi đơn giản hiệu quả hơn nhiều như bạn mô tả.
Vality

Câu trả lời:


24

Tôi có thể đưa ra một vài nhược điểm của việc sử dụng printf (). Hãy nhớ rằng "hệ thống nhúng" có thể dao động từ thứ gì đó có vài trăm byte bộ nhớ chương trình đến hệ thống điều khiển QNX RTOS gắn trên giá đỡ đầy đủ với gigabyte RAM và terabyte bộ nhớ không biến đổi.

  • Nó đòi hỏi một nơi nào đó để gửi dữ liệu. Có thể bạn đã có một cổng gỡ lỗi hoặc lập trình trên hệ thống, có thể bạn không có. Nếu bạn không (hoặc người bạn không làm việc), nó không tiện dụng lắm.

  • Đây không phải là một chức năng nhẹ trong tất cả các bối cảnh. Đây có thể là một vấn đề lớn nếu bạn có một bộ vi điều khiển chỉ có vài K bộ nhớ, bởi vì liên kết trong printf có thể tự ăn hết 4K. Nếu bạn có một vi điều khiển 32K hoặc 256K, có lẽ đó không phải là vấn đề, hãy để một mình nếu bạn có một hệ thống nhúng lớn.

  • Nó ít hoặc không sử dụng để tìm một số loại vấn đề nhất định liên quan đến cấp phát hoặc ngắt bộ nhớ và có thể thay đổi hành vi của chương trình khi có bao gồm các câu lệnh hay không.

  • Nó khá vô dụng để đối phó với những thứ nhạy cảm với thời gian. Bạn sẽ tốt hơn với máy phân tích logic và máy hiện sóng hoặc máy phân tích giao thức hoặc thậm chí là trình giả lập.

  • Nếu bạn có một chương trình lớn và bạn phải biên dịch lại nhiều lần khi bạn thay đổi các câu lệnh printf xung quanh và thay đổi chúng, bạn có thể lãng phí rất nhiều thời gian.

Điều tốt cho nó - đó là một cách nhanh chóng để xuất dữ liệu theo cách được định dạng sẵn mà mọi lập trình viên C đều biết cách sử dụng - đường cong học tập bằng không. Nếu bạn cần phải tạo ra một ma trận cho bộ lọc Kalman mà bạn đang gỡ lỗi, có thể rất tốt khi nhổ nó theo định dạng mà MATLAB có thể đọc được. Chắc chắn tốt hơn là nhìn vào các vị trí RAM một lần trong trình gỡ lỗi hoặc trình giả lập .

Tôi không nghĩ đó là một mũi tên vô dụng trong máy rung, nhưng nó nên được sử dụng một cách tiết kiệm, cùng với gdb hoặc các trình gỡ lỗi khác, trình giả lập, máy phân tích logic, máy hiện sóng, công cụ phân tích mã tĩnh, công cụ bao phủ mã, v.v.


3
Hầu hết printf()việc thực hiện không an toàn theo luồng (nghĩa là không tái đăng ký) không phải là kẻ giết người thỏa thuận, nhưng cần lưu ý khi sử dụng nó trong môi trường đa luồng.
JRobert

1
@JRobert mang đến một điểm tốt .. và ngay cả trong môi trường không có hệ điều hành, thật khó để thực hiện nhiều thao tác gỡ lỗi trực tiếp hữu ích cho ISR. Tất nhiên, nếu bạn đang thực hiện printf () hoặc toán học dấu phẩy động trong ISR, cách tiếp cận có thể bị tắt.
Spehro Pefhany

@JRobert Công cụ gỡ lỗi nào mà các nhà phát triển phần mềm làm việc trong môi trường đa luồng (trong cài đặt phần cứng nơi sử dụng Máy phân tích logic và máy hiện sóng không thực tế) có?
Minh Trần

1
Trước đây, tôi đã tự in printf (); sử dụng tương đương đặt chân trần () hoặc putchar () để nhổ dữ liệu rất súc tích vào một thiết bị đầu cuối; lưu trữ dữ liệu nhị phân trong một mảng mà tôi đã kết xuất và giải thích sau khi chạy thử; đã sử dụng cổng I / O để nháy đèn LED hoặc tạo xung để thực hiện các phép đo thời gian bằng máy hiện sóng; nhổ một con số vào D / A & được đo bằng VOM ... Danh sách này dài bằng trí tưởng tượng của bạn và ngược lại lớn như ngân sách của bạn! :)
JRobert

19

Ngoài một số câu trả lời hay khác, hành động gửi dữ liệu tới một cổng ở tốc độ truyền nối tiếp có thể hoàn toàn chậm đối với thời gian vòng lặp của bạn và có tác động đến cách thức hoạt động của phần còn lại của chương trình (như BẤT K deb gỡ lỗi quá trình).

Như những người khác đã nói với bạn, không có gì "xấu" khi sử dụng kỹ thuật này, nhưng nó, giống như nhiều kỹ thuật gỡ lỗi khác, cũng có những hạn chế. Miễn là bạn biết và có thể giải quyết những hạn chế này, đó có thể là một khả năng cực kỳ thuận tiện để giúp bạn lấy mã chính xác.

Các hệ thống nhúng có độ mờ nhất định, nói chung, làm cho việc gỡ lỗi có một chút vấn đề.


8
+1 cho "hệ thống nhúng có độ mờ nhất định". Mặc dù tôi sợ tuyên bố này chỉ có thể được hiểu bởi những người có kinh nghiệm tốt khi làm việc với nhúng, nhưng nó làm cho một bản tóm tắt ngắn gọn, súc tích về tình huống. Nó gần với định nghĩa của "nhúng", thực sự.
njahnke

5

Có hai vấn đề chính bạn sẽ gặp phải khi cố gắng sử dụng printftrên vi điều khiển.

Đầu tiên, nó có thể là một nỗi đau để dẫn đầu ra đến cổng chính xác. Không phải lúc nào. Nhưng một số nền tảng khó khăn hơn những nền tảng khác. Một số tệp cấu hình có thể được ghi lại kém và có thể cần nhiều thử nghiệm.

Thứ hai là trí nhớ. Một printfthư viện đầy đủ có thể là LỚN. Đôi khi, bạn không cần tất cả các công cụ định dạng mặc dù và các phiên bản chuyên dụng có thể có sẵn. Chẳng hạn, stdio.h AVR được cung cấp chứa ba printfkích cỡ và chức năng khác nhau.

Do việc thực hiện đầy đủ tất cả các tính năng được đề cập trở nên khá lớn, nên vfprintf()có thể chọn ba hương vị khác nhau bằng cách sử dụng các tùy chọn liên kết. Mặc định vfprintf()thực hiện tất cả các chức năng được đề cập ngoại trừ chuyển đổi điểm nổi. Một phiên bản thu nhỏ vfprintf()có sẵn chỉ thực hiện các phương tiện chuyển đổi chuỗi và số nguyên rất cơ bản, nhưng chỉ #có thể chỉ định tùy chọn bổ sung bằng cách sử dụng các cờ chuyển đổi (các cờ này được phân tích chính xác từ đặc tả định dạng, nhưng sau đó chỉ cần bỏ qua).

Tôi đã có một ví dụ mà không có thư viện có sẵn và tôi có bộ nhớ tối thiểu. Vì vậy, tôi không có lựa chọn nào khác ngoài sử dụng macro tùy chỉnh. Nhưng việc sử dụng printfhay không thực sự là một trong những gì sẽ phù hợp với yêu cầu của bạn.


Có thể downvoter giải thích những gì trong câu trả lời của tôi là không chính xác để tôi có thể tránh sai lầm của mình trong các dự án trong tương lai?
embedded.kyle

4

Để thêm vào những gì Spehro Pefhany đã nói về "những thứ nhạy cảm với thời gian": hãy lấy một ví dụ. Giả sử bạn có một con quay hồi chuyển mà hệ thống nhúng của bạn đang thực hiện 1.000 phép đo mỗi giây. Bạn muốn gỡ lỗi các phép đo này, vì vậy bạn cần in chúng ra. Vấn đề: in chúng ra khiến hệ thống quá bận để đọc 1.000 phép đo mỗi giây, khiến cho bộ đệm của con quay bị tràn, khiến dữ liệu bị hỏng được đọc (và in). Và do đó, bằng cách in dữ liệu, bạn đã làm hỏng dữ liệu, khiến bạn nghĩ rằng có lỗi trong việc đọc dữ liệu khi có thể thực sự không có. Một cái gọi là bọ rùa.


cười lớn! Có phải "heisenorms" thực sự là một thuật ngữ kỹ thuật? Tôi đoán nó có liên quan đến việc đo trạng thái hạt và Nguyên lý của Heisenburg ...
Zeta.Investigator

3

Lý do lớn hơn để không gỡ lỗi với printf () là nó thường không hiệu quả, không đầy đủ và không cần thiết.

Không hiệu quả: printf () và kin sử dụng rất nhiều flash và RAM so với những gì có sẵn trên một vi điều khiển nhỏ, nhưng sự kém hiệu quả hơn nằm ở việc gỡ lỗi thực tế. Thay đổi những gì đang được ghi lại đòi hỏi phải biên dịch lại và lập trình lại mục tiêu, làm chậm quá trình. Nó cũng sử dụng hết UART mà bạn có thể sử dụng để thực hiện công việc hữu ích.

Không đầy đủ: Chỉ có rất nhiều chi tiết bạn có thể xuất qua một liên kết nối tiếp. Nếu chương trình bị treo, bạn không biết chính xác ở đâu, chỉ là đầu ra cuối cùng đã hoàn thành.

Không cần thiết: Nhiều bộ vi điều khiển có thể được gỡ lỗi từ xa. JTAG hoặc các giao thức độc quyền có thể được sử dụng để tạm dừng bộ xử lý, xem lén các thanh ghi và RAM và thậm chí thay đổi trạng thái của bộ xử lý đang chạy mà không cần phải biên dịch lại. Đây là lý do tại sao trình gỡ lỗi nói chung là một cách gỡ lỗi tốt hơn so với các câu lệnh in, ngay cả trên PC có hàng tấn không gian và sức mạnh.

Thật không may là nền tảng vi điều khiển phổ biến nhất cho người mới, Arduino, không có trình gỡ lỗi. AVR hỗ trợ gỡ lỗi từ xa, nhưng giao thức debugWIRE của Atmel là độc quyền và không có giấy tờ. Bạn có thể sử dụng bảng dev chính thức để gỡ lỗi với GDB, nhưng nếu bạn có điều đó thì có lẽ bạn không còn quá lo lắng về Arduino nữa.


Bạn không thể sử dụng các con trỏ hàm để chơi với những gì đang được ghi lại và thêm toàn bộ tính linh hoạt?
Scott Seidman

3

printf () không tự hoạt động. Nó gọi nhiều chức năng khác và nếu bạn có ít không gian ngăn xếp, bạn hoàn toàn không thể sử dụng nó để gỡ lỗi các vấn đề gần với giới hạn ngăn xếp của bạn. Tùy thuộc vào trình biên dịch và vi điều khiển, chuỗi định dạng cũng có thể được đặt trong bộ nhớ, thay vì được tham chiếu từ flash. Điều này có thể tăng đáng kể nếu bạn tiêu mã của mình bằng các câu lệnh printf. Đây là một vấn đề lớn trong môi trường Arduino - những người mới bắt đầu sử dụng hàng chục hoặc hàng trăm câu lệnh printf đột nhiên gặp phải các vấn đề dường như ngẫu nhiên vì họ ghi đè lên đống của họ với ngăn xếp của họ.


2
Mặc dù tôi đánh giá cao phản hồi mà chính downvote cung cấp, nó sẽ hữu ích hơn cho tôi và những người khác nếu những người không đồng ý giải thích các vấn đề với câu trả lời này. Tất cả chúng ta đều ở đây để học hỏi và chia sẻ kiến ​​thức, xem xét chia sẻ kiến ​​thức của bạn.
Adam Davis

3

Ngay cả khi người ta muốn nhổ dữ liệu ra một số dạng bảng điều khiển ghi nhật ký, thì printfchức năng nói chung không phải là một cách tốt để làm điều đó, vì nó cần kiểm tra chuỗi định dạng đã truyền và phân tích cú pháp khi chạy; ngay cả khi mã không bao giờ sử dụng bất kỳ trình xác định định dạng nào ngoài %04X, bộ điều khiển thường sẽ cần bao gồm tất cả các mã sẽ được yêu cầu để phân tích các chuỗi định dạng tùy ý. Tùy thuộc vào bộ điều khiển chính xác mà người dùng đang sử dụng, việc sử dụng mã có thể hiệu quả hơn nhiều như:

void log_string(const char *st)
{
  int ch;
  do
  {
    ch = *st++;
    if (ch==0) break;
    log_char(ch);
  } while(1);
}
void log_hexdigit(unsigned char d)
{
  d&=15;
  if (d > 9) d+=7;
  log_char(d+'0');
}
void log_hexbyte(unsigned char b)
{ log_hexdigit(b >> 4); log_hexdigit(b); }
void log_hexi16(uint16_t s)
{ log_hexbyte(s >> 8); log_hexbyte(s); }
void log_hexi32(uint32_t i)
{ log_hexbyte(i >> 24); log_hexbyte(i >> 16); log_hexbyte(i >> 8); log_hexbyte(i); }
void log_hexi32p(uint32_t *p) // On a platform where pointers are less than 32 bits
{ log_hexi32(*p); }

Trên một số bộ vi điều khiển PIC, log_hexi32(l)có thể sẽ có 9 hướng dẫn và có thể mất 17 (nếu lở ngân hàng thứ hai), trong khi log_hexi32p(&l)sẽ mất 2. log_hexi32pChức năng có thể được viết dài khoảng 14 lệnh, do đó, nó sẽ tự trả nếu được gọi hai lần .


2

Một điểm mà không có câu trả lời nào khác đã đề cập: Trong một vi cơ bản (IE chỉ có vòng lặp chính () và có lẽ một vài ISR ​​đang chạy bất cứ lúc nào, không phải là một hệ điều hành đa luồng) nếu nó gặp sự cố / dừng / bị bị mắc kẹt trong một vòng lặp, chức năng in của bạn sẽ không xảy ra .

Ngoài ra, mọi người đã nói "không sử dụng printf" hoặc "stdio.h chiếm nhiều dung lượng" nhưng không được đưa ra nhiều thay thế - embed.kyle đề cập đến các lựa chọn thay thế đơn giản, và đó chính xác là điều bạn có thể nên làm làm như một vấn đề tất nhiên trên một hệ thống nhúng cơ bản. Một thói quen cơ bản để phun ra một vài ký tự trong UART có thể là một vài byte mã.


Nếu printf của bạn không xảy ra, bạn đã học được nhiều về việc codeis của bạn có vấn đề.
Scott Seidman

Giả sử bạn chỉ có một printf có thể xảy ra, vâng. Nhưng các ngắt có thể kích hoạt hàng trăm lần trong khi cần một lệnh gọi printf () để lấy bất cứ thứ gì ra khỏi UART
John U
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.