임베디드 시스템 디버깅에 printf ()가 나쁜 이유는 무엇입니까?


16

를 사용하여 마이크로 컨트롤러 기반 프로젝트를 디버깅하는 것은 나쁜 일이라고 생각합니다 printf().

출력 할 사전 정의 된 장소가 없으며 귀중한 핀을 소비 할 수 있음을 이해할 수 있습니다. 동시에 사람들이 사용자 정의 DEBUG_PRINT()매크로 를 사용하여 IDE 터미널에 출력하기 위해 UART TX 핀을 소비하는 것을 보았습니다 .


12
누가 나쁜지 말해 줬어? "보통 최고는 아닙니다"는 규정되지 않은 "나쁜"과 다릅니다.
Spehro Pefhany

6
오버 헤드가 얼마나되는지에 대한이 모든 이야기는 "I 'm here"메시지를 출력하기 만하면 printf가 전혀 필요하지 않으며 UART에 문자열을 보내는 루틴 일뿐입니다. 또한 UART를 초기화하는 코드는 100 바이트 미만의 코드 일 수 있습니다. 두 16 진수 값을 출력하는 기능을 추가해도 그다지 많이 증가하지는 않습니다.
tcrosley

7
@ChetanBhargava-C 헤더 파일은 일반적으로 실행 파일에 코드를 추가하지 않습니다. 선언이 포함되어 있습니다. 나머지 코드에서 선언 된 내용을 사용하지 않으면 해당 코드가 연결되지 않습니다. printf물론 을 사용 하면 구현하는 데 필요한 모든 코드가 printf실행 파일에 연결됩니다. 그러나 헤더가 아닌 코드에서 사용했기 때문입니다.
Pete Becker

2
@ChetanBhargava 앞에서 설명한 것처럼 문자열을 출력하기 위해 간단한 루틴을 롤링하는 경우 <stdio.h>를 포함하지 않아도됩니다 ( '\ 0'이 나타날 때까지 문자를 UART로 출력) '
tcrosley

2
@tcrosley 나는 훌륭한 현대 컴파일러가 있다면 아마도이 조언이 어리석은 것이라고 생각합니다. 형식 문자열 gcc없이 간단한 경우에 printf를 사용하고 대부분의 사람들은 설명하는 것처럼 훨씬 효율적인 간단한 호출로 대체합니다.
Vality

답변:


24

printf () 사용의 몇 가지 단점을 생각 해낼 수 있습니다. "내장 된 시스템"은 수백 바이트의 프로그램 메모리가있는 것에서부터 기가 바이트의 RAM과 테라 바이트의 비 휘발성 메모리가있는 완전한 랙 마운트 QNX RTOS 구동 시스템에 이르기까지 다양합니다.

  • 데이터를 보낼 곳이 필요합니다. 시스템에 이미 디버그 또는 프로그래밍 포트가 있거나 없을 수도 있습니다. 당신이하지 않으면 (또는 당신이 가지고있는 사람이 작동하지 않는) 그것은 매우 편리하지 않습니다.

  • 모든 상황에서 가벼운 기능은 아닙니다. printf에서 링크하는 것 자체가 4K를 모두 소비 할 수 있기 때문에 단지 몇 K의 메모리를 가진 마이크로 컨트롤러를 가지고 있다면 이것은 큰 문제가 될 수 있습니다. 32K 또는 256K 마이크로 컨트롤러를 사용하는 경우 큰 임베디드 시스템을 사용하는 경우는 물론 문제가되지 않습니다.

  • 메모리 할당 또는 인터럽트와 관련된 특정 종류의 문제를 찾는 데 거의 사용되지 않으며 명령문이 포함되거나 포함되지 않을 때 프로그램의 동작을 변경할 수 있습니다.

  • 타이밍에 민감한 것들을 다루는 데는 쓸모가 없습니다. 로직 분석기와 오실로스코프, 프로토콜 분석기 또는 시뮬레이터를 사용하는 것이 좋습니다.

  • 큰 프로그램이 있고 printf 문을 변경하고 변경할 때 여러 번 다시 컴파일해야하는 경우 많은 시간을 낭비 할 수 있습니다.

좋은 점은 모든 C 프로그래머가 제로 학습 곡선을 사용하는 방법을 알고있는 미리 형식화 된 방식으로 데이터를 출력하는 빠른 방법입니다. 디버깅중인 Kalman 필터에 대한 매트릭스를 추출해야하는 경우 MATLAB이 읽을 수있는 형식으로 추출하는 것이 좋습니다. 디버거 또는 에뮬레이터에서 한 번에 하나씩 RAM 위치를 보는 것보다 낫습니다. .

나는 그것이 화살통에 쓸모없는 화살이라고 생각하지 않지만 gdb 또는 다른 디버거, 에뮬레이터, 논리 분석기, 오실로스코프, 정적 코드 분석 도구, 코드 범위 도구 등과 함께 드물게 사용해야합니다.


3
대부분의 printf()구현은 스레드 안전 (즉, 재진입 불가)이 아니므로 거래 킬러가 아니지만 다중 스레드 환경에서 사용할 때는 명심해야합니다.
JRobert

1
@JRobert는 좋은 지적을 제시합니다. 심지어 운영 체제가없는 환경에서도 ISR을 직접 디버깅하는 것은 매우 어렵습니다. 물론 ISR에서 printf () 또는 부동 소수점 수학을 수행하는 경우 접근 방식이 꺼져있을 수 있습니다.
Spehro Pefhany

@JRobert 멀티 스레드 환경에서 작업하는 소프트웨어 개발자는 어떤 디버깅 도구를 사용합니까 (로직 분석기 및 오실로스코프 사용이 실용적이지 않은 하드웨어 환경에서)?
Minh Tran

1
과거에는 스레드 안전 printf (); 맨발 puts () 또는 putchar () 등가를 사용하여 매우 간결한 데이터를 터미널에 뱉어냅니다. 테스트 실행 후 덤프 및 해석 한 배열에 이진 데이터를 저장했습니다. I / O 포트를 사용하여 LED를 깜박이거나 펄스를 생성하여 오실로스코프로 타이밍을 측정합니다. D / A에 숫자를 뱉어 내고 VOM으로 측정 한 것입니다 ...이 목록은 상상할 수있는만큼 길고 예산만큼이나 큽니다! :)
JRobert

19

다른 정밀한 답변 외에도 직렬 보오 속도로 포트로 데이터를 전송하는 작업은 루프 시간과 관련하여 매우 느리게 진행되며 나머지 프로그램 기능에 영향을 줄 수 있습니다 (모든 디버그 가능) 방법).

다른 사람들이 당신에게 말했듯이,이 기술을 사용하는 것에 대해 "나쁜"것은 없지만 다른 디버그 기술과 마찬가지로 한계가 있습니다. 이러한 제한 사항을 알고 처리 할 수 ​​있으면 코드를 올바르게 작성하는 데 도움이되는 매우 편리한 방법이 될 수 있습니다.

임베디드 시스템은 일반적으로 디버깅을 약간의 문제로 만드는 특정 불투명도를 가지고 있습니다.


8
"임베디드 시스템에 특정 불투명도가 있음"은 +1입니다. 비록이 진술이 임베디드 작업에 대해 괜찮은 경험을 가진 사람들에 의해서만 이해 될 수있을 것이라 생각하지만, 그것은 상황을 훌륭하고 간결하게 요약합니다. 실제로는 "임베디드"의 정의에 가깝습니다.
njahnke

5

printf마이크로 컨트롤러 에서 사용하려고 할 때 두 가지 주요 문제가 있습니다 .

첫째, 출력을 올바른 포트로 파이프하는 것은 고통 스러울 수 있습니다. 항상 그런 것은 아닙니다. 그러나 일부 플랫폼은 다른 플랫폼보다 더 어렵습니다. 일부 구성 파일이 잘못 문서화되어 많은 실험이 필요할 수 있습니다.

두 번째는 기억입니다. 완전한 printf라이브러리는 BIG 일 수 있습니다. 때로는 모든 형식 지정자가 필요하지는 않지만 특수 버전을 사용할 수 있습니다. 예를 들어, stdio.h AVR 에서 제공하는printf 다양한 크기와 기능의 세 가지가 있습니다.

언급 된 모든 기능의 전체 구현이 상당히 커지므로 vfprintf()링커 옵션을 사용하여 세 가지 다른 특징을 선택할 수 있습니다. 기본값 vfprintf()은 부동 소수점 변환을 제외한 언급 된 모든 기능을 구현합니다. vfprintf()기본 정수 및 문자열 변환 기능 만 구현 하는 최소화 된 버전을 사용할 수 있지만 #변환 플래그를 사용하여 추가 옵션 만 지정할 수 있습니다 (이 플래그는 형식 사양에서 올바르게 구문 분석 된 다음 무시 됨).

사용 가능한 라이브러리가없고 최소한의 메모리가있는 인스턴스가있었습니다. 따라서 사용자 정의 매크로를 사용하는 것 외에는 선택의 여지가 없었습니다. 그러나 사용 printf여부는 실제로 요구 사항에 맞는 것 중 하나입니다.


다운 보터가 향후 프로젝트에서 실수를 피할 수 있도록 내 대답에 무엇이 잘못되었는지 설명해 주시겠습니까?
embedded.kyle

4

Spehro Pefhany가 "타이밍에 민감한 것"에 대해 말한 것에 덧붙이 자 : 예를 들어 보자. 임베디드 시스템이 초당 1,000 회 측정하는 자이로 스코프가 있다고 가정 해 봅시다. 이 측정 값을 디버그하려고하므로 인쇄해야합니다. 문제점 : 인쇄하면 시스템이 너무 바빠서 초당 1,000 회 측정 값을 읽지 못하므로 자이로 스코프 버퍼가 오버 플로우되어 손상된 데이터를 읽고 인쇄합니다. 따라서 데이터를 인쇄하면 데이터가 손상되어 실제로 데이터가 없을 때 데이터를 읽는 데 버그가 있다고 생각하게됩니다. 소위 heisenbug.


롤! "heisenbug"는 실제로 기술적 인 용어입니까? 입자 상태와 Heisenburg의 원리 측정과 관련이 있다고 생각합니다.
Zeta.Investigator

3

printf ()로 디버깅하지 않는 더 큰 이유는 일반적으로 비효율적이며 부적절하고 불필요하기 때문입니다.

비효율적 : printf () 및 kin은 소형 마이크로 컨트롤러에서 사용 가능한 것에 비해 많은 플래시 및 RAM을 사용하지만 실제 디버깅에서는 비 효율성이 더 큽니다. 기록되는 내용을 변경하려면 대상을 다시 컴파일하고 다시 프로그래밍해야하므로 프로세스 속도가 느려집니다. 또한 유용한 작업을 수행하는 데 사용할 수있는 UART를 사용합니다.

부적절 : 직렬 링크를 통해 출력 할 수있는 세부 사항이 너무 많습니다. 프로그램이 중단되면 마지막 출력이 완료된 위치를 정확히 알 수 없습니다.

불필요 : 많은 마이크로 컨트롤러를 원격으로 디버깅 할 수 있습니다. JTAG 또는 독점 프로토콜을 사용하여 프로세서를 일시 중지하고 레지스터와 RAM을 들여다 볼 수 있으며 심지어 재 컴파일하지 않고도 실행중인 프로세서의 상태를 변경할 수도 있습니다. 그렇기 때문에 디버거는 일반적으로 공간과 전력이 많은 PC에서도 print 문보다 더 나은 디버깅 방법입니다.

불행히도 초보자를위한 가장 일반적인 마이크로 컨트롤러 플랫폼 인 Arduino에는 디버거가 없습니다. AVR은 원격 디버깅을 지원하지만 Atmel의 debugWIRE 프로토콜은 독점적이며 문서화되어 있지 않습니다. 공식 개발자 보드를 사용하여 GDB로 디버깅 할 수 있지만 Arduino에 대해 걱정하지 않아도됩니다.


함수 포인터를 사용하여 기록중인 내용을 재생하고 유연성을 추가 할 수 없습니까?
Scott Seidman

3

printf ()는 저절로 작동하지 않습니다. 다른 많은 함수를 호출하며 스택 공간이 부족하면 스택 한도에 가까운 문제를 디버깅하는 데 전혀 사용할 수 없습니다. 컴파일러와 마이크로 컨트롤러에 따라 포맷 문자열은 플래시에서 참조되는 것이 아니라 메모리에 배치 될 수도 있습니다. printf 문으로 코드를 작성하면 크게 늘어날 수 있습니다. Arduino 환경에서 큰 문제입니다. 수십 또는 수백 개의 printf 문을 사용하는 초보자는 스택으로 힙을 덮어 쓰기 때문에 갑자기 임의의 문제가 발생합니다.


2
downvote 자체가 제공하는 피드백에 감사하지만 동의하지 않는 사람들 이이 답변의 문제를 설명하면 저와 다른 사람들에게 더 도움이 될 것입니다. 우리는 모두 지식을 배우고 공유하기 위해 여기 있습니다.
Adam Davis

3

데이터를 어떤 형태의 로깅 콘솔에 뱉어 내려하더라도, printf함수는 전달 된 형식 문자열을 검사하고 런타임에 구문 분석해야하기 때문에 일반적으로 그렇게하는 좋은 방법이 아닙니다. 코드가 이외의 형식 지정자를 사용하지 않더라도 %04X컨트롤러는 일반적으로 임의의 형식 문자열을 구문 분석하는 데 필요한 모든 코드를 포함해야합니다. 사용하는 정확한 컨트롤러에 따라 다음과 같은 코드를 사용하는 것이 훨씬 더 효율적일 수 있습니다.

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); }

일부 PIC 마이크로 컨트롤러에서는 log_hexi32(l)9 개의 명령어가 필요하고 17 개 ( l두 번째 뱅크에있는 경우) 가 필요할 수 있지만 2 개가 log_hexi32p(&l)걸립니다. log_hexi32p함수 자체는 약 14 개 명령어로 작성 될 수 있으므로 두 번 호출하면 자체적으로 지불 .


2

다른 답변이 언급되지 않은 한 가지 점 : 기본 마이크로 (IE는 main () 루프와 멀티 스레드 OS가 아닌 언제든지 ISR이 실행 중입니다) 충돌 / 중지 / 도착 루프에 걸리면 인쇄 기능이 단순히 발생하지 않습니다 .

또한 사람들은 "printf를 사용하지 마십시오"또는 "stdio.h는 많은 공간을 차지합니다"라고 말했지만 많은 대안을 제시하지 않았습니다. 물론 기본 임베디드 시스템에서 수행합니다. UART에서 몇 개의 문자를 분출하는 기본 루틴은 몇 바이트의 코드 일 수 있습니다.


printf가 발생하지 않으면 코드에 문제가있는 부분에 대해 많은 것을 배웠습니다.
Scott Seidman

발생할 수있는 printf가 하나만 있다고 가정합니다. 그러나 printf () 호출이 UART에서 무언가를 얻는 데 걸리는 시간에 인터럽트가 수백 번 발생할 수 있습니다.
John U
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.