실무에서 쓰이지 않지만 시험에서만 출제되는 C언어 시험 문제의 세계를 고발(?)하고자 한다

고발이라고 하면 좀 자극적이긴 하지만 사실 특별한 내용은 아니다. 대부분 C언어 하시는 분들은 예전부터 생각해온 것일지도...


사실 요새 몇몇 기관의 시험문제를 출제, 감수 하면서 현실과 동떨어진 시험의 세계를 느낀 후 적는 글이다.

어떤 시험을 출제하느냐고 묻지는 말았으면 한다. 보안서약상 구체적인 기관이나 정보를 이야기할 수는 없다.


시험 문제 출제를 할 때면 출제를 요구하는 기관에서 과목, 출제 목표, 세부사항 등을 일일히 정해준다. 

따라서 누가 문제를 내든지 비슷한 형태가 나올 수 밖에 없다.


예를 들면 다음과 같은 요구 사항이 있다고 치자.


 과목

 대분류

 소분류

 세부 사항/출제 목표

 C언어

 예약어

 연산자

 연산자 우선 순위

 C언어

 문자열  입력방법

 scanf 함수군

 시스템 시그널

 시그널 처리기

 signal 함수


1. 연산자 우선순위는 실무에선 필요없다.

앞서 첫번째 요구 사항인 연산자 우선 순위를 테스트한다고 가정하자. 

출제자는 어쩔 수 없이 말도 안되는 복합적인 연산을 한 행에서 실행하도록 코딩해야만 한다. 

예를 들면 아래 코드와 같이 복합 연산 후 최종값을 물어본다든지 해야 한다.


  int x = 4, y = 50, z = 100;
  int sum = --x*2 + (y>>1) / 3 * 5 + (z & 0x10);


위에서 sum의 값은 무엇일까? 대부분은 맞추겠지만 좀 짜증이 날 것이다. 


그런데 실무에서 저딴 식으로 짜놓으면 선임에게 혼쭐이 난다. 실무에서 모든 연산은 소괄호로 묶어서 명확하게 작성하도록 한다. 더군다나 저렇게 한 행에 복잡한 수식을 만들기보다는 간단한 수식으로 여러 행으로 풀어서 작성한다. 만일 소괄호를 쓰지 않고 한 행에 저렇게 복잡하게 수식을 넣어버리면 나중에 큰 사고가 터질 가능성이 있다.(수정을 하거나, 컴파일러를 교체할 때 연산자 우선순위 오류를 일으키는 경우가 있기 때문이다.)


위의 예에서는 x, y, z가 한 번만 등장하지만 2번 이상 등장하는 경우, 예를 들어 --x와 x가 섞여서 나오는 경우에는 해석 순서는 undefined이므로 어떤 일이 발생할 지 모른다.(C언어는 sequence point가 중요하기 때문에 한 행에 여러 sequence가 섞여 있으면 안된다.)[1]


undefined behaviour란 정의되지 않은 행위, 달리 말해 implementation에 따라 다른 결과가 나올 수도 있는 행위므로 심각한 오류를 발생 시킬 확률이 높아진다. 실제로 위 행동에 대해 표준안에는 다음과 같이 쓰여있다.


Between the previous and next sequence point an object shall have its stored value modified at most once by the evaluation of an expression. Furthermore, the prior value shall be read only to determine the value to be stored. - ISO:IEC 9899:1999 C99 standard. p67


수치 연산 오류는 시스템의 신뢰도에 심각한 영향을 주기 때문에 절대로 한 행에 연산자 우선순위를 감안한 프로그래밍이나 sequence point가 엉망인 코드를 작성하면 안된다. 개인적인 생각으로 연산자 우선 순위 같은 문제는 나오지 말아야만 한다. 오히려 교수님이나 강사분들이 강의할 때 저런 식으로 프로그래밍 하지 말라고 가르쳐야 한다.


2. 묵시적 타입 캐스팅(implicit type casting)

C언어에서는 묵시적으로 이뤄지는 타입 캐스팅이 있다. 예를 들어 정수형을 나눌 때 피연산값이 실수형이면 알아서 타입캐스팅이 일어나는 것이다. 문제는 이런 묵시적인 타입 캐스팅은 실무에선 문제를 발생시킬 수 있어서 명시적인 타입 캐스팅(explicit type casting)으로 바꾼다는 점이다.


만일 정수형을 꼭 써야 한다면 나눗셈에서 반올림이나 반내림 때문에 문제가 발생할 수 있다. 그래서 미리 100이든 1000이든 곱해서 자리 올림을 하든지, 아니면 실수형으로 바꿔서 계산하기도 한다.

결국 타입 캐스팅이나 나눗셈 문제를 꼬는 것은 그다지 좋은 문제는 아니다. 시험에서 좋지 않은 버릇을 들여서 실무에 투입된 직원은 언젠가는 사고 칠 수 있는 폭탄이 되기도 한다.


3. 비비꼬인 포인터 문제

C언어 시험에 종종 등장하는 복잡한 포인터 문제도 실무에선 쓰이지 않는다. 심하게는 3~4중 포인터를 만들어서 값을 물어보는 경우도 있는데, 3~4중 포인터가 실무에서 쓰이던가? 개념을 잡기 위함이라고 변명하는 사람도 있지만, 쓰지도 않는 방법으로 가르치는 것은 좋은 교습방법이 아니라고 생각된다.


이상하게 꼬였네... 스크류 포인터이상하게 꼬였네... 포인터


실무에서 2중 포인터 이상을 쓰는 일은 극히 드물다. 2중 포인터도 reentrant 함수나 스레드 함수에서나 주로 등장하지 그 외에는 거의 등장하지 않는다.


또한 2중 포인터 이상을 사용하는 경우라 하더라도 포인터 변수를 typedef 해서 연산자를 줄이는 경우가 많다. 실제로 GNU의 glib 라이브러리가 이런 방식으로 작성되어있다.


왜 복잡한 포인터 연산을 쓰지 않는가?

= 가장 큰 이유는 복잡한 포인터 연산은 현대적인 CPU와 컴파일러의 최적화(optimization)에 방해물이 되기 때문이다. 포인터 연산이 복잡해지면 CPU와 컴파일러가 각 변수의 의존성(dependancy)을 판단하기 어려워진다. 그 결과 비순차 실행(out of order)이나 분기 예측(branch prediction)을 하기 어려워지고, 코드의 성능이 떨어지게 된다.[각주:1]


포인터 연산이 꼭 필요한 경우라면 restrict pointer를 사용할 수 있는지 생각해보자. 

restrict pointer를 쓸 수 있는 조건이라면 포인터를 사용하더라도 최적화에 도움을 줄 수 있다.


결국 복잡한 포인터 연산을 스크류바처럼 빙글빙글 꼬으면 뭔가 멋지게 코딩 한 것처럼 보이겠지만, 그건 하수(下手)들이나 쓰는 기술이다. 중급 이상의 고수들 눈에는 쓸데없는 짓으로 보일 뿐이다. 포인터 연산에 대한 환상이 있다면 버리는 것이 좋다. 포인터는 주로 call by reference 용도가 중요하니 그 쪽만 확실하게 익혀두는 것을 추천한다. (주의 : 포인터가 call by reference 용도로 쓰인다는 것이지 C언어에서 reference를 지원한다는 의미는 아니다. 엄밀하게 C언어는 언어 자체에서 reference를 지원하지 않는다.)


4. scanf 함수

scanf는 입력을 받는 대표적인 함수이다. C언어에서는 누구든지 배우는 기능이지만 사실 실무에서는 쓰이지 않는다. 왜냐하면 scanf는 개행 문자나 공백 처리에 문제가 있기 때문이다. 그래서 실무에서는 fgets를 통해서 문자열을 받아 파싱하는 구조를 더 많이 사용한다.


최근에는 fgets보다 발전된 새로운 getline이나 getdelim 함수를 더 많이 사용한다.(C언어: fscanf를 대체하는 getline 함수)


그럼에도 불구하고 모든 시험에 빠지지 않고 등장하는 것이 scanf 함수다. 

개인적으로 이 함수는 이제 가볍게 배우고, 새로운 getline, getdelim 함수를 열심히 배워야 한다고 생각된다.


5. signal 함수

유닉스, 리눅스 계열에서 시그널 처리기를 설치하는 함수로 signal()을 쓰는 책이나 시험이 많다. 문제는 signal() 함수는 신뢰성에 결함이 있어 표준안에서도 sigaction()으로 대체하라고 쓰여 있다.  더군다나 이건 십수년전부터 나온 이야기다. 그럼에도 불구하고 아직도 signal()로 쓰여진 책들을 보면 좀 암담하다. 


SUS 유닉스 표준안에서 signal 함수 부분을 보면 다음과 같이 나와있다.[각주:2]


APPLICATION USAGE

The sigaction() function provides a more comprehensive and reliable mechanism for controlling signals; new applications should use sigaction() rather than signal().

- The Open Group Base Specifications Issue 7. IEEE Std 1003.1-2008.


signal을 보고 다시 SUS의 sigaction 매뉴얼 페이지를 보면 왜 signal을 쓰면 안되는지 자세하게 적혀있다. 여기에 적으면 너무 길어지니 나중에 다시 포스팅하도록 하겠다.


하지만 결론만 말하자면 signal() 함수 대신에 sigaction()을 써야만 한다는 것이다.


6. 그 외의 짜투리.

위에 5가지를 적었는데, 이 외에도 시험과 실무간에 갭이 있는 기법들이 있다. 예를 들면 다음과 같은 것들은 시험에서만 보이고 실무에서는 뒤의 것으로 대체되어 쓰인다.

  • 재귀함수 - 쓰면 혼남. 많이 혼남.(돈이 왔다갔다 하는 곳에서는 싫어하지만, 다른 파트는 좀 다를수도 있음)
  • fflush(stdin) - 이건 비표준. 쓰면 안되는데 좋지 않은 C책에 많이 등장함.
  • fork-exec 기법 - posix_spawn로 대체
  • vfork 기법 - fork로 대체
  • strcpy, strcat - stpcpy, stpncpy로 대체
    (guarantee null terminated string = strlcpy, but non-standard)[각주:3]
  • select 멀티플렉싱 - poll, epoll로 대체
  • gethostbyname, getservbyname, inet_addr, inet_aton - getaddrinfo로 대체
  • gethostbyaddr, getservbyport, inet_ntoa - getnameinfo로 대체
  • 무한 루프 재시도 - queue, 타이머 모델로 대체

fork-exec, vfork, strcpy 함수군, select/pselect, gethostbyname ... 등은 구식 함수(removed functions)이며 기능에 문제가 있는 경우도 있다. 따라서 현재 실무에서는 사용 빈도가 줄어드는 함수이다. 몇몇 함수는 표준에서도 사용을 지양하라는 기능도 있다.




PS.

얼마전 C언어 국내책을 추천해달라고 해서 서점 간김에 책을 이리저리 살펴봤다.


결론은 국내서적 중에 C언어를 제대로 설명하는 책은 없었다. 

쉽게 설명하는 책은 내용이 틀렸고, 내용이 올바른 책은 너무 어렵게 설명되어있고...(그나마 올바르게 설명된 책은 외국번역서... -_-)

판매순위 1위에 랭크된 영광스런 책도 내용은 그닥이었다. 국제 C언어 표준을 안보시고 책을 쓰시나?


더보기


2015년 기준으로 가장 쓸만한 C언어 기초 책은 KNK, C primer plus이며, 이에 대한 내용을 포스팅 해두었다.

C언어 공부법과 책 추천 - http://sunyzero.tistory.com/225



PS2.
앞으로 시험 출제 총괄이 된다면, C99의 기법 정도는 꼭 포함시킬테다.(C11은 아직 영글지 않은 기술이고, 지원하는 컴파일러도 적어서 스킵) 시대가 어느 때인데 아직도 ANSI C에 맞춰서 내는지... 답답할 때가 많다. 

시험이란 현재 사용하는 기술과 이론, 국제표준에 부합해야 한다. 그렇지 않으면 시험 자체가 아무런 의미가 없다고 생각된다.


* 히스토리

2015-03-28 C언어 공부법과 책 추천 링크 추가

2014-02-21 PS2 추가

2013-08-07 PS 추가

2013-04-15 strlcpy 코멘트 추가

2013-03-18 작성


* 레퍼런스

[1] Sequence point, https://en.wikipedia.org/wiki/Sequence_point



  1. 김민장. (2010). 프로그래머가 몰랐던 멀티코어 CPU 이야기. 서울, 한빛미디어. [본문으로]
  2. Single UNIX Spec., http://www.opengroup.org/standards/platform [본문으로]
  3. C String handling, http://en.wikipedia.org/wiki/C_string_handling [본문으로]
저작자 표시
신고
  1. ㅎㅎ 2013.03.18 19:40 신고

    잘 보고 갑니다.ㅎㅎ

  2. salbang 2013.03.21 10:34 신고

    잘 봤습니다. 그런데 strcpy는 왜 strncpy가 아니라 stpcpy로 대체 하나요? stpcpy도 위험하지 않나요?

    • sunyzero 2013.03.21 12:00 신고

      네, 잘못 썼네요. 지적 감사합니다. 수정해두겠습니다.
      (그런데 실무에선 오버헤드를 피하기 위해 중복된 길이 검사를 하지 않는 경우도 많습니다. 그래서 그냥 stpcpy도 많이 쓰긴 합니다.)

    • salbang 2013.03.21 13:29 신고

      사실 제가 stpcpy를 이 글 보고 알았습니다 ^^ 용도를 잘 몰랐었는데 저는 버퍼 오버플로우의 관점에서만 생각 했었는데 stpcpy를 strcat 대신 쓰면 여러 문자열을 계속 이어 붙이기 할 때 널 터미네이티드 스트링의 길이를 매번 재 계산하는 수고를 피할 수 있겠네요. 암튼 좋은 함수 배우고 갑니다 ^^

    • sunyzero 2013.03.21 17:49 신고

      stpcpy는 2006년에 추가된 함수라서 아직 사용이 빈번한 것 같지는 않더라구요.

      salbang님이 언급하신대로 stpcpy는 문자열의 끝을 재계산하지 않게 됩니다. 그래서 strcat보다 내부적으로도 효율이 좋죠.

  3. Che 2013.03.21 17:51 신고

    재귀함수 쓰면 많이 혼나나요?? ㅎㅎㅎ

    • sunyzero 2013.03.21 20:46 신고

      사실 재귀함수를 쓸 수 있는 문제도 거의 없습니다만... 그럼에도 불구하고 재귀함수 쓰면 아마 많이 혼날겁니다. ^^

  4. +요롱이+ 2013.03.22 18:50 신고

    너무 잘 보고 갑니다.
    남은 하루 평안하고 유익한 시간 되시기 바랍니다!

  5. 명태랑 짜오기 2013.03.25 12:38 신고

    좋은 정보 잘 보고 갑니다.
    즐겁게 월요일 시작 하세요^^

  6. Konn 2013.03.27 20:36 신고

    공부와 시험은 실무를 위한 것인데, 정작 실무와 동 떨어져 있으니 무슨 의미가 있으련지 싶군요...

    • sunyzero 2013.03.27 21:22 신고

      그렇죠. 실무 능력 평가 시험에서 실무에 쓰이지 않는 기법을 시험치니... -_-;;

  7. 제임스 2013.04.02 13:26 신고

    재귀함수의 경우에는 조금 특수해 보이기는 하는데
    Stack이 작은 embedded system의 경우에는 금물일테지만,
    고급 engine을 만드는 경우에는 (compiler, interpreter, DB engine 등)
    recursive를 사용하지 않으면 해결할 수 없는 경우가 종종 있는 것 같아, 살짝 적어봅니다.

    그나저나, 아주 즐겁게 봤습니다. ^^
    몇가지 새로 배웠구요. 고맙습니다.

    • sunyzero 2013.04.02 19:29 신고

      컴파일러나 인터프리터는 제 분야가 아니라서 확답은 못하지만 그 외의 경우에서 recursion은 루프로 대체가 가능합니다. 만일 꼭 recursion을 해야 하는 구조라면 설계가 잘못되었는지 살펴보라고 배웠던 기억이 있습니다. ^^

      그리고 recursion을 제거해야 하는 이유는 스택오버플로우도 있지만 성능을 떨어뜨리는 주원인인 것도 있습니다.

  8. 잊혀진 2013.04.19 15:59 신고

    정말 정말 좋은 내용 잘 보고 배우고 갑니다.
    요즘 C99 표준에 대해 많이 공부중이거든요..ㅎㅎ
    이런 분들이 많아져야 하는데 ㅎㅎㅎ
    여담이지만, 왠지 현장에서 C의 영역이 JAVA로 많이 대체되고...
    C의 입지가 좁아지는것 같아서 씁쓸하네요..

    • sunyzero 2013.04.20 08:07 신고

      C언어는 강력하지만, 생산성이 낮은 게 흠이죠.
      C99라면 전웅님 책으로 공부하시나요?

    • 잊혀진 2013.04.22 17:34 신고

      댓글 감사합니다.^^
      생산성이 낮다.....는 것의 의미는 이론적으로 언어들을 비교했을 때 표현하는 말인것 같아요....
      실무에선 제 짧은 생각엔 그 언어를 사용하는 개발자의 역량/능력/언어이해도/경험등에 따라 생산성은 달라진다고 생각합니다.
      댓글을 보고 말씀하신 전웅님 책이 어떤 책인지 후다닥 찾아보았네요 ㅎㅎㅎㅎ
      책은 음...아직 책을 볼 실력은 안되고요..ㅠㅠ..
      그냥 C99 Draft 보고 있습니다....

    • sunyzero 2013.04.22 22:54 신고

      유닉스 계열(리눅스 포함) 개발자시라면 C99 외에 포괄적인 시스템 인터페이스 표준인 SUS도 같이 보셔야 좋을 것 같습니다.

  9. webuniversity 2013.06.06 22:17 신고

    좋은 내용 감사합니다
    그런데 select함수는 왜 epoll로 써야하나요??

    • sunyzero 2013.06.07 13:30 신고

      2가지 이유가 있습니다.
      첫째로 select는 입력값을 보존하지 않기 때문에 함수에서 사용하는 fd_set 구조체를 관리하는 함수와 여러가지 추가 코딩이 필수입니다.
      둘째로 select는 stateless function으로 I/O처리가 많습니다.

      첫번째 이유를 개선한 것이 poll 함수로서 입력된 값을 보존해주어 코딩이 깔끔해집니다. 둘째로 stateful function으로 디자인되어 커널에 상태를 보존시켜 I/O처리를 줄이고, 반응을 높인 것이 epoll입니다.

      하지만 간단한 키보드 입력이나 굉장히 적은 fd를 감시할 때는 select를 써도 괜찮습니다. 단 이 경우에도 poll로 대체하는 것이 좋기는 합니다.

    • webuniversity 2013.06.08 13:16 신고

      감사합니다!

  10. 밥줄 2013.08.14 12:22 신고

    잘보고 갑니다 그런데 연산자들을 소괄호로 묶지 않으면 오류가 생기는 부분은 이해할 수 있는데 연산자의 순서를 바꿔서 프로그래밍 해야하는 경우가 있나요?

    그리고 혹시 초대장 남으신다면 하나만 부탁드려요

    liminax@naver.com

  11. 2014.04.22 17:09

    비밀댓글입니다

  12. 나그네 2015.08.21 10:40 신고

    전웅님 책으로 c99공부중인데 이 책도 오류가 많이 있나요?

    • sunyzero 2015.08.21 15:12 신고

      전웅님 책은 좋지만 초급도서가 아니기에 예외로 뺐습니다. 책은 좋습니다만 설명이 장황한 감이 있긴 합니다. 물론 어느 정도 기초가 닦여진 다음에 보면 이해하기 어렵지는 않습니다. 단 초급때 보면 질려서 C를 다시 보지 않을 가능성이 있습니다.

  13. 학생 2016.12.19 16:38 신고

    올해 컴공 1학년을 마친 재학생입니다 1학년엔 거의 전공수업이없지만 c언어랑 java를 들었는데요 제가 잠시 방황?을 하는바람에 수업도 제대로 못듣고 시험을 위한 공부만 했었습니다. 정신을 차리고 공부를 이번방학에 하려는대 방학에 다음학기에 배우는 c++을 미리공부할까 생각 중이였는데 c언어에대한 이해가 많이 부족한거같어서 질문드립니다 knk원서는 시중에 판매되는 한국인 저자들의 기초입문 책들이랑 내용이 다른가요??
    그리고 다른언어를 미리 공부하는것에 관해서는 어떻게생각하시나요?? 언어말고 다른것을 공부하는게 좋다 생각하시면 추천해주시면 감사하겠습니다

    • sunyzero 2016.12.19 18:10 신고

      네, 많이 다릅니다. 제대로 된 책들은 쉬운 것만을 강조하지는 않습니다. 쉬운 것보다 더 중요한 것은 제대로 된 내용이냐 아니냐의 여부입니다.

      심지어 어떤 책은 쉽다는 것을 강조하기위해 그림이 절반이고, 어떤 책은 포인터를 제대로 다뤘다면서 쓸데없는 현란한 포인터만 다루는데, 이런 것들은 오히려 잘못된 내용을 배우게 합니다.
      ---------
      다른 언어로는 파이썬을 추천합니다. 공부의 목적뿐만 아니라 실무에서도 많이 사용합니다.

      나중에 2,3학년이 되면서 파이썬으로 자료구조나 알고리즘을 코딩하는 것을 해보면 큰 도움이 될 것입니다.

    • 학생 2016.12.19 19:17 신고

      그렇다면 이번방학에는한번 제대로 씨언어 공부하는게 좋겠죠??괜히 이것저것하는것보다