printf-버그의 근원? [닫은]


9

printf내 코드에서 추적 / 로깅 목적으로 많은 것을 사용하고 있는데 프로그래밍 오류의 원인이라는 것을 알았습니다. 나는 항상 삽입 연산자 ( <<)가 다소 이상한 것으로 나타 났지만 대신 그것을 사용함으로써 이러한 버그 중 일부를 피할 수 있다고 생각하기 시작했습니다.

비슷한 계시를받은 사람이 있습니까? 아니면 그냥 빨대를 잡는 것입니까?

일부는 포인트를 빼앗아

  • 나의 현재 생각은 type-safety가 printf를 사용하는 것의 이점보다 중요하다는 것입니다. 실제 문제는 형식 문자열과 형식이 안전하지 않은 가변성 함수를 사용하는 것입니다.
  • 어쩌면 나는 <<stl 출력 스트림 변형을 사용하지 않을 것입니다. 그러나 매우 안전한 유형 안전 메커니즘을 사용하는 것이 확실합니다.
  • 많은 추적 / 로깅은 조건부이지만 테스트에서 버그를 놓치지 않기 위해 항상 코드를 실행하고 거의 가지지 않습니다.

4
printfC ++ 세계에서? 여기에 뭔가 빠졌습니까?
user827992

10
@ user827992 : C ++ 표준에 참조로 C 표준 라이브러리가 포함되어 있다는 사실이 누락 되었습니까? printfC ++에서 사용하는 것은 합법적 입니다. (좋은 아이디어인지는 또 다른 질문입니다.)
Keith Thompson

2
@ user827992 : printf몇 가지 장점이 있습니다. 내 대답을 참조하십시오.
Keith Thompson

1
이 질문은 아주 경계선입니다. "어떻게 생각하니?"질문은 종종 닫힙니다.
dbracey

1
@vitaut 나는 추측합니다 (팁 감사합니다). 나는 공격적인 중재에 약간 당황했습니다. 프로그래밍 상황에 대한 흥미로운 토론을 실제로 장려하지는 않습니다.
John Leidegren

답변:


2

printf, 특히 성능에 관심이있는 경우 (예 : sprintf 및 fprintf) 정말 이상한 해킹입니다. 가상 함수와 관련된 작은 성능 오버 헤드로 인해 C ++을 사용하는 사람들이 C의 io를 방어하기 위해 계속 나아갈 것입니다.

예, 컴파일 시간에 100 % 알 수있는 출력 형식을 파악하기 위해 런타임에 까다로운 형식 문자열을 구문 분석 할 수없는 형식 코드를 사용하여 엄청나게 이상한 점프 테이블에서 구문 분석하십시오!

물론 이러한 형식 코드는 그들이 나타내는 유형과 일치하도록 만들 수 없었습니다. 너무 쉬울 것입니다 ...이 (강한 유형의) 언어로 만드는 % llg 또는 % lg인지 여부를 조회 할 때마다 알려줍니다 인쇄 / 스캔 할 유형을 수동으로 파악하고 32 비트 이전 프로세서 용으로 설계되었습니다.

C ++의 형식 너비와 정밀도 처리는 부피가 크고 구문 설탕을 사용할 수 있지만 C의 주요 io 시스템 인 기괴한 핵을 방어해야한다는 것을 인정하지는 않습니다. 절대 기본은 어느 언어에서나 매우 쉽습니다 (어쨌든 디버그 코드에 대해 사용자 정의 오류 함수 / 오류 스트림과 같은 것을 사용해야하지만) 중간 경우는 C에서 정규 표현식과 유사합니다 (쓰기 쉽고 구문 분석 / 디버그가 어렵습니다) ), C에서는 복잡한 경우가 불가능합니다.

(표준 컨테이너를 전혀 사용하지 않는다면 std::cout << my_list << "\n";my_list가 type 인 디버그 와 같은 작업을 수행 할 수있는 빠른 템플릿 연산자 << 과부하를 직접 작성하십시오 list<vector<pair<int,string> > >.)


1
표준 C ++ 라이브러리의 문제점은 대부분의 화신이 다음 operator<<(ostream&, T)을 호출하여 구현한다는 것입니다 sprintf. 성능은 sprintf최적이 아니지만 이로 인해 일반적으로 iostream의 성능이 훨씬 나빠집니다.
Jan Hudec

@ JanHudec :이 시점에서 약 10 년 동안 사실이 아니 었습니다. 실제 인쇄는 동일한 기본 시스템 호출로 수행되며 C ++ 구현은 종종이를 위해 C 라이브러리를 호출하지만 std :: cout을 printf를 통해 라우팅하는 것과는 다릅니다.
jkerian

16

C 스타일의 혼합 printf()(또는 puts()또는 putchar()C ++ 또는 ...) 출력 - 스타일의 std::cout << ...출력은 안전하지 않을 수 있습니다. 올바르게 기억하면 별도의 버퍼링 메커니즘을 사용할 수 있으므로 출력이 의도 한 순서대로 나타나지 않을 수 있습니다. (AProgrammer가 주석에서 언급했듯이 이것을 sync_with_stdio해결합니다).

printf()기본적으로 형식이 안전하지 않습니다. 인수 예상 유형은 형식 문자열에 의해 결정된다 ( "%d"필요 int로 촉진 또는 무언가를 int, "%s"필요 char*하지만 정의되지 않은 동작에 인수 결과의 잘못된 유형을 통과하는 제대로 종료 C 스타일의 문자열 등을 가리켜 야하는이) 진단 가능한 오류가 아닙니다. gcc와 같은 일부 컴파일러는 형식 불일치에 대해 합리적으로 훌륭한 경고 작업을 수행하지만 형식 문자열이 리터럴이거나 컴파일 타임에 알려진 경우 (가장 일반적인 경우)에만 가능합니다. 언어에는 경고가 필요하지 않습니다. 잘못된 유형의 인수를 전달하면 임의로 나쁜 일이 발생할 수 있습니다.

반면 C ++의 스트림 I / O는 <<연산자가 여러 유형에 대해 오버로드 되기 때문에 훨씬 안전 합니다. std::cout << x유형을 지정할 필요가 없습니다 x. 컴파일러는 모든 유형에 맞는 코드를 생성합니다 x.

반면에 printf의 서식 옵션은 훨씬 편리합니다. 소수점 뒤에 3 자리로 부동 소수점 값을 인쇄하려면 사용할 수 "%.3f"있으며 동일한 printf호출 내에서도 다른 인수에는 영향을 미치지 않습니다 . setprecision반면에 C ++ 는 스트림 상태에 영향을 미치며 스트림을 이전 상태로 복원하는 데주의를 기울이지 않으면 나중에 출력을 엉망으로 만들 수 있습니다. (이것은 내 개인 애완 동물의 피입니다. 피할 수있는 깨끗한 방법이 없으면 의견을 말하십시오.)

둘 다 장단점이 있습니다. 가용성은 printfC 배경이 있고 더 익숙하거나 C 소스 코드를 C ++ 프로그램으로 가져 오는 경우에 특히 유용합니다. std::cout << ...C ++에 더 관용적이며 유형 불일치를 피하기 위해 많은주의를 기울일 필요가 없습니다. 둘 다 유효한 C ++입니다 (C ++ 표준에는 대부분의 C 표준 라이브러리가 참조로 포함됨).

그건 아마도 사용하는 것이 가장 std::cout << ...코드에서 작동 할 수있는 다른 C ++ 프로그래머를 위해,하지만 당신은 중 하나를 사용할 수 있습니다 - 특히 추적 코드에서 멀리 던질 거라고.

물론 디버거를 사용하는 방법을 배우는 데 약간의 시간이 필요합니다 (그러나 일부 환경에서는 불가능할 수도 있습니다).


원래 질문에 혼합에 대한 언급이 없습니다.
dbracey

1
@dbracey : 아니요,하지만 가능한 단점으로 언급 할 가치가 있다고 생각했습니다 printf.
Keith Thompson

6
동기화 문제는을 참조하십시오 std::ios_base::sync_with_stdio.
AProgrammer

1
+1 다중 스레드 앱에서 std :: cout을 사용하여 디버그 정보를 인쇄하는 것은 100 % 쓸모가 없습니다. 적어도 printf를 사용하면 사람이나 기계가 인터리브하거나 파싱 할 수 없을 것입니다.
James

@ 제임스 : std::cout인쇄 된 각 항목에 대해 별도의 호출을 사용 하기 때문 입니까? 인쇄하기 전에 출력 라인을 문자열로 수집하여이 문제를 해결할 수 있습니다. 물론 한 번에 한 항목 씩 인쇄 할 수도 있습니다 printf. 한 번의 통화로 회선을 인쇄하는 것이 더 편리합니다.
Keith Thompson

2

문제는 두 개의 매우 다른 표준 출력 관리자가 혼합되어 발생했을 가능성이 큽니다. 각각의 작은 STDOUT에 대한 자체 의제가 있습니다. 당신은 그들이 구현하는 방법에 대한 보장을 얻을 수없고, 그들이 파일 기술자 옵션을 충돌 설정하는 것이 완벽하게 가능, 그것은 서로 다른 일을하는 두 시도 등 또한, 삽입 운영자가 하나 개의 큰 이상을 가지고는 printf: printf이 작업을 수행하게됩니다 :

printf("%d", SomeObject);

반면에 <<그렇지 않습니다.

참고 : 디버깅을 위해 사용하지 않는 printfcout. fprintf(stderr, ...)및을 사용 cerr합니다.


원래 질문에 혼합에 대한 언급이 없습니다.
dbracey

물론 객체의 주소를 인쇄 할 수는 있지만 큰 차이점은 printf유형 안전하지 않다는 것입니다. 현재의 생각은 유형 안전이을 사용하는 이점보다 중요하다는 것입니다 printf. 문제는 실제로 형식 문자열 이며 형식이 안전하지 않은 가변성 함수입니다.
John Leidegren

@JohnLeidegren :하지만 SomeObject포인터 가 아닌 경우 어떻게해야 합니까? 컴파일러가 나타내는 임의의 이진 데이터를 얻을 것 SomeObject입니다.
Linuxios

나는 당신의 대답을 거꾸로 읽은 것 같습니다 ... nvm.
John Leidegren

1

스트림을 좋아하지 않는 많은 그룹 (예 : Google)이 있습니다.

http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Streams

(삼각형을 열면 토론을 볼 수 있습니다.) Google C ++ 스타일 가이드에는 매우 합리적인 조언이 많이 있습니다.

트레이드 오프는 스트림이 더 안전하지만 printf는 더 명확하게 읽을 수 있으며 원하는 형식을 정확하게 얻는 것이 더 쉽다는 것입니다.


2
Google 스타일 가이드는 훌륭하지만 범용 가이드에 적합하지 않은 몇 가지 항목이 포함되어 있습니다 . (괜찮아, 결국 구글에서 실행되는 코드에 대한 구글 가이드이기 때문에 괜찮다 .)
Martin Ba

1

printf유형 안전 부족으로 인해 버그가 발생할 수 있습니다. 로 전환하지 않고 그 해결의 몇 가지 방법이있다 iostream'의 <<운영자 더 많은 서식 복잡는 :

  • GCC 및 Clang과 같은 일부 컴파일러는 선택적 printf으로 형식 문자열을 printf인수와 비교하여 검사 할 수 있으며 일치하지 않는 경우 다음과 같은 경고를 표시 할 수 있습니다.
    경고 : 변환은 'int'유형을 지정하지만 인수에는 'char *'유형이 있습니다.
  • typesafeprintf의 스크립트는 사전 처리 할 수 printf그들을 입력 안전 할 스타일의 전화를.
  • 같은 라이브러리 Boost.FormatFastFormat은 당신이 사용하게 printf-like 형식 문자열을 (Boost.Format의 특히 거의 동일 printf)로 유지하면서 iostreams'유형의 안전과 유형 확장.

1

printf 구문은 기본적으로 괜찮으며 일부는 입력이 불분명합니다. C #, Python 및 기타 언어가 매우 유사한 구문을 사용하는 이유가 잘못되었다고 생각하십니까? C 또는 C ++의 문제점 : 언어의 일부가 아니므로 올바른 구문 (*)에 대해 컴파일러가 확인하지 않으므로 속도를 최적화 할 경우 일련의 기본 호출로 분해되지 않습니다. 크기를 최적화하면 printf 호출이 더 효율적일 수 있습니다! C ++ 스트리밍 구문은 아무 것도 아닙니다. 작동하지만 형식 안전성이 있지만 자세한 구문은 ... bleh입니다. 나는 그것을 사용하지만 기쁨없이 의미합니다.

(*) 일부 컴파일러는이 검사와 거의 모든 정적 분석 도구를 수행합니다 (Lint를 사용하므로 printf에 아무런 문제가 없었습니다).


1
편리한 구문 ( )과 형식 안전성 을 결합한 Boost.Format 이 있습니다 format("fmt") % arg1 % arg2 ...;. 많은 구현에서 내부적으로 sprintf 호출을 생성하는 문자열 스트림 호출을 생성하므로 성능이 약간 더 떨어집니다.
Jan Hudec

0

printf제 생각에는 CPP 스트림 출력보다 변수를 처리하는 훨씬 유연한 출력 도구입니다. 예를 들면 다음과 같습니다.

printf ( "%d in ANSI = %c\n", j, j ); /* Perfectly valid... if a char ISN'T printing right, I'd just check the integer value to make sure it was okay. */

그러나 CPP <<연산자 를 사용하려는 경우 특정 방법에 대해 CPP 연산자를 오버로드하는 경우입니다 PersonData.

ostream &operator<<(ostream &stream, PersonData obj)
{
 stream << "\nName: " << name << endl;
 stream << " Number: " << phoneNumber << endl;
 stream << " Age: " << age << endl;
 return stream;
}

이를 위해서는 말하는 것이 훨씬 효과적입니다 (가정 a은 PersonData의 객체 라고 가정 )

std::cout << a;

보다:

printf ( "Name: %s\n Number: %s\n Age: %d\n", a.name, a.number, a.age );

전자는 캡슐화의 원칙 (특정, 개인 멤버 변수를 알 필요가 없음)과 훨씬 더 일치하며 읽기도 쉽습니다.


0

printfC ++ 에서는 사용하지 않아야합니다 . 이제까지. 그 이유는 올바르게 지적했듯이 버그의 원천이며 사용자 정의 유형 인쇄 및 C ++에서 거의 모든 것이 사용자 정의 유형이어야한다는 사실이 고통 스럽기 때문입니다. C ++ 솔루션은 스트림입니다.

그러나 스트림을 사용자가 볼 수있는 출력에 적합하지 않게하는 중요한 문제가 있습니다! 문제는 번역입니다. gettext 매뉴얼 에서 빌린 예제는 다음 과 같이 작성하고 싶다고 말합니다.

cout << "String '" << str << "' has " << str.size() << " characters\n";

이제 독일어 번역가가 와서 말합니다. 네, 독일어로 된 메시지는

n Zeichen lang ist die Zeichenkette ' s '

그리고 이제 당신은 조각이 뒤섞여 있어야하기 때문에 곤경에 처했습니다. 많은 구현조차도 printf이것에 문제 가 있다고 말해야합니다 . 확장을 지원하지 않으면 사용할 수 있습니다.

printf("%2$d Zeichen lang ist die Zeichenkette '%1$s'", ...);

Boost.Format의 지원이는 printf 스타일의 형식과이 기능이 있습니다. 그래서 당신은 작성합니다 :

cout << format("String '%1' has %2 characters\n") % str % str.size();

불행히도 내부적으로 문자열 스트림을 생성하고 <<연산자를 사용하여 각 비트를 포맷하고 많은 구현에서 <<연산자가 내부적으로 호출 하기 때문에 약간의 성능 저하 가 sprintf있습니다. 정말로 원한다면 더 효율적인 구현이 가능할 것으로 생각합니다.


-1

stl악의적이든 아니든 사실 외에는 쓸모없는 일을 많이하고 있습니다. 일련의 수준의 가능한 실패를 추가 하는 일련의 코드로 코드를 디버깅 하십시오 printf.

디버거를 사용하고 예외에 대한 내용과이를 잡아서 던지는 방법을 읽으십시오. 실제로 필요한 것보다 더 장황하지 마십시오.

추신

printf C에서 사용되는 C ++에 사용됩니다. std::cout


디버거 대신 추적 / 로깅을 사용하지 않습니다.
John Leidegren
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.