<div>쓰다보니 쓸 데 없이 길어졌네요..</div> <div>바쁘신 분은 세줄요약만 보셔도 됩니다....</div> <div><br></div> <div>------------------------------</div> <div><br></div>과제 때문인지 업무 때문인지는 모르겠지만, <div>C언어의 scanf 함수 사용에 대한 질문 글이 꾸준히 올라오네요. ^^;</div> <div><br></div> <div>사실, scanf 를 업무용 프로그램에서 쓰는 일은 '거의' <strike>라고 쓰고 '네버'라고 읽음</strike> 없습니다.</div> <div>왜냐 하면, 여러 질문에 나와 있는 '그 문제점들' 때문이죠.. ㅋㅋ.</div> <div>그리고, scanf 함수의 실제 구현과 동작은 각 시스템(Unix/Linux/Windows...)별로 다르고,</div> <div>컴파일러별로 다릅니다. C 표준에 정의된 사양만 그것에 부합하도록 같을 것이고, undefined behavior는 제조사마다 다를 겁니다.</div> <div><br></div> <div>아무튼, 저도 scanf 질문글이 올라올 때마다,</div> <div><br></div> <div>"뭐, 또, 엔터키 입력된 것 버퍼에서 안지워서 그렇겠지..."</div> <div><br></div> <div>요정도 생각하고 들어 옵니다. 그리고 실제로 <strike>예지력 상승을</strike> 확인하고 나가죠...</div> <div><br></div> <div>그러다 문득, 정말로 scanf 가 어떤 짓을 하도록 표준에 정의되어 있는 걸까...</div> <div>왜 그따위로 만들어서 여러 착한 사람들을 못살게 굴까 궁금해지기 시작했습니다.</div> <div><br></div> <div>그래서, 일단, 소스코드를 좀 봐야겠다... 싶어서 찾아 봤죠.</div> <div>물론 시스템별로 구현이 다르니, scanf 소스코드도 여러 버전이 있습니다.</div> <div>제가 찾은 것은 요기 <a target="_blank" href="https://sourceware.org/git/?p=glibc.git;a=blob;f=stdio-common/vfscanf.c;h=e0d224530cc67498c3fa5b37838d5bdcdf08eb28;hb=HEAD">https://sourceware.org/git/?p=glibc.git;a=blob;f=stdio-common/vfscanf.c;h=e0d224530cc67498c3fa5b37838d5bdcdf08eb28;hb=HEAD</a> 에 있습니다.</div> <div>그리고 요기 <a target="_blank" href="http://mirror.fsf.org/pmon2000/3.x/src/lib/libc/scanf.c">http://mirror.fsf.org/pmon2000/3.x/src/lib/libc/scanf.c</a> 에는 좀 간략한 버전이 있더군요.</div> <div><br></div> <div>좀 읽어 보려다가... 금세... <strike>아몰랑</strike>.. 시전..</div> <div>뭐 소스를 꼭 이해해야 하는 건 아니잖아, 어차피 시스템마다 구현도 다른 걸... 이라는 자기합리화... -_-;;</div> <div><br></div> <div>뭐, 아무튼, scanf 를 쓰면서 오는 혼란 중 가장 큰 것은,</div> <div>scanf 가 input buffer 를 '비울 때도 있고 안비울 때도 있기' 때문이라고 봅니다.</div> <div>게다가, scanf 의 표준에는 scanf 가 동작하다가 예외를 만났을 때의 상황을 일일이 정의하지 않고 있기 때문에,</div> <div>Windows/Linux/Unix/Mac 등등에서 서로 다른 시스템과 컴파일러를 사용하면서</div> <div>서로 묻고 답하는 와중에, 또다시 혼란이 생기고... 하여... 문제가 더 복잡해 지지 않았나... 마.. 그런 생각이 듭니다.</div> <div><br></div> <div>흔히 scanf 관련 질문 올라올 때는 아래와 같은 패턴이죠:</div> <div><br></div> <div>scanf("%d", &var1);</div> <div>printf("%d\n", var1);</div> <div> <div><br></div> <div>scanf("%d", &var2);</div> <div>printf("%d\n", var2);</div></div> <div><br></div> <div>저기서, 첫번째 scanf에 입력을 했는데 왜 두 번째 scanf가 실행이 안되고 쭉 지나가 버리는가...하는 것이 가장 많지요?</div> <div>거기에 대한 답은 여러분도 익히 아시다시피 첫번째 scanf 문에서 입력을 할 때,</div> <div>마지막에 enter 를 넣은 것이 input buffer에 남아 있어서 그렇다. 그러니 아래와 같이 고쳐라..는 겁니다.</div> <div><br></div> <div> <div>scanf("%d", &var1);</div> <div>printf("%d\n", var1);</div> <div> <div><br></div> <div>getchar();</div> <div><br></div> <div>scanf("%d", &var2);</div> <div>printf("%d\n", var2);</div></div></div> <div><br></div> <div>요렇게 하면 해결이 된다! 맞습니다. 하지만, 예외처리가 완전히 된 것은 아닙니다.</div> <div>만약, 첫 번째 scanf 함수에서 사용자가 숫자가 아닌 문자를 '여러 개' 입력하면,</div> <div>또는 숫자와 문자를 합쳐서 11aaaa 와 같이 입력하면 또 다른 예외상황이 발생하게 됩니다.</div> <div><br></div> <div>그래서, 입력을 받고 나면 무조건 버퍼를 비워라!</div> <div>라는 뜻으로 아래와 같이 하는 방법도 제시합니다.</div> <div><br></div> <div> <div> <div>scanf("%d", &var1);</div> <div>printf("%d\n", var1);</div> <div> <div><br></div> <div>scanf("%s", str);</div> <div><br></div> <div>scanf("%d", &var2);</div> <div>printf("%d\n", var2);</div></div></div></div> <div><br></div> <div>요렇게 하면, 입력 버퍼가 비워지는 것은 맞습니다. 대부분의 경우 동작도 잘 될겁니다.</div> <div>다만, 저기서 사용자가 아주 긴.. 문자열을 입력하게 되면, 즉, str 이라는 버퍼의 크기를</div> <div>넘는 문자열을 입력하게 되면, 또 다시 예외상황이 발생하게 됩니다.</div> <div><br></div> <div>그래서 이번에는, scanf로 비우지 말고, <span style="font-size:9pt;line-height:1.5;">아래와 같이 비워라... 하는 방법도 있습니다.</span></div> <div><span style="font-size:9pt;line-height:1.5;"><br></span></div> <div> <div> <div>scanf("%d", &var1);</div> <div>printf("%d\n", var1);</div> <div> <div><br></div> <div>fflush(stdin);</div> <div><br></div> <div>scanf("%d", &var2);</div> <div>printf("%d\n", var2);</div></div></div></div> <div><br></div> <div>요렇게요. 근데, 문제가 있습니다.</div> <div>fflush 라는 함수는 output buffer 에 있는 것을 모두 해당 stream으로 보내버리라는 것이지 (변기에서 물 내리는 것 처럼요)</div> <div>input stream에 대해서 동작하도록 C 표준에 있지는 않은 것 같습니다. (참조: <a target="_blank" href="http://c-faq.com/stdio/stdinflush2.html">http://c-faq.com/stdio/stdinflush2.html</a>)</div> <div>그런데, 더 큰 문제는, fflush(stdin) 이라는 함수가 어떤 시스템 환경(아마도 Windows)에서는 동작하기도 하는 모양입니다??? (참조: <a target="_blank" href="https://kldp.org/node/29034">https://kldp.org/node/29034</a>)</div> <div><br></div> <div>그리고, scanf("%d", &var) 등으로 읽을 때,</div> <div>어떤 시스템에서는 숫자 입력하고 난 후 'enter'를 누른 것을 input buffer에서 없애버리고,</div> <div>어떤 시스템에서는 남겨두고.. 하니까 이게... 더 혼란이 오는 것 같습니다.</div> <div>그리고, 이건 순전히 제 추측인데요.</div> <div>Windows 와 Linux 에서 개행(New Line)을 처리하는 문자가 다릅니다. 아시죠?</div> <div>Windows : 0D0A (Carriage-Return + Line-Feed)</div> <div>Linux/Unix/Mac : 0A</div> <div>요렇게 다른데요, Windows(또는 DOS)에서의 scanf는 0D는 읽어서 없애지만,</div> <div>0A는 남겨두는 것 같습니다.</div> <div>그러니, linux등에서 잘 되던 소스가 window에서 실행하면 이상하게 안되는 현상이 발생하기도 할 것이고요...</div> <div><br></div> <div>그런데, 실제로 VisualC/C++ 등에서 구현된 scanf 의 소스코드를 보지 않았기 때문에,</div> <div>0D만 읽어서 없애고, 뒤따라오는 0A는 내버려 두는 일을 실제로 하는 지는 확인되지 않았습니다.</div> <div><br></div> <div>scanf에 대해서 또하나 아셔야 할 것은,</div> <div>input stream에서 format에 지정된 타입의 값을 읽지 못하면 <span style="font-size:9pt;line-height:1.5;">input stream buffer를 비우지 않는다는 것인데요,</span></div> <div>사실 이렇게 말하는 것 보다는 아래와 같이 말하는 것이 더 정확할 겁니다.</div> <div>"Input Stream에서 format에 지정된 type의 값을 읽게 되면 해당 값을 읽을 때까지의 버퍼는 비운다" 가 맞을 겁니다.</div> <div>무슨 말인고 하면,</div> <div><br></div> <div>scanf("%d", &var); 로 사용자 입력을 읽을 때, 사용자가 입력을:</div> <div><br></div> <div>1) 1234</div> <div>2) abcd</div> <div>3) ab12</div> <div>4) 12ab</div> <div><br></div> <div>의 네 가지 경우가 있다고 가정하면,</div> <div>----var에 들어가는 값------버퍼에 남아있는 문자---</div> <div>1) ==> 1234 ________________(없음)</div> <div>2) ==> (없음)________________abcd</div> <div><span style="font-size:9pt;line-height:1.5;">3) ==> (없음)________________ab12</span></div> <div><span style="font-size:9pt;line-height:1.5;">4) ==> 12 ___________________ab</span></div> <div>와 같이 된다는 겁니다.</div> <div>위에서 %d 로(즉, 숫자) 읽으라고 했으니, scanf는 input buffer에서 숫자를 찾습니다.</div> <div>1)의 경우에는 숫자가 찾아졌으므로 읽어서 변수에 할당하죠, 그리고 읽어낸 값을 inuput buffer에서 지웁니다.</div> <div>2)의 경우에는 숫자가 없으므로 변수는 변동되지 않지만, abcd 라는 문자는 '여전히' input buffer에 남아 있습니다.</div> <div>3)의 경우에도 2)와 다를 바 없습니다.</div> <div>4)의 경우에는 먼저 들어 온 숫자 12를 읽고 그 다음 문자(ab)를 만나게 되어서 12만 값을 넣고, 버퍼에서 12를 지우고, ab는 남겨 놓습니다.</div> <div><br></div> <div>자, 이제 거의 다 왔네요 ^^;</div> <div>scanf 의 함수 이름이, SCAN 이잖아요... getInput 이나 readInput 이 아니고...</div> <div>그 말은, 뭔가를 scan....한다는 뜻이겠죠? 즉, input buffer를 scan 한다는 겁니다.</div> <div>scanf는 input buffer의 내용을 scan하면서, 주어진 format에 지정된 data-type에 matching되는 값을 찾아서</div> <div>주어진 변수에 할당을 하는 일을 하는 것이지, 단순히 키보드를 입력 받아서 그것을 실시간으로 변환하는 것이 아니기 때문에,</div> <div>이름을 get...이나 read..가 아닌 scan으로 지은 것이 아닌가.. 합니다.</div> <div><br></div> <div> <div style="text-align:left;"><img src="http://thimg.todayhumor.co.kr/upfile/201505/1431753085gSZecL6dTBS7.jpg" width="531" height="390" alt="scan.jpg" style="border:none;"></div><br></div> <div>그럼 어떻게 scanf를 써야 하는가...?</div> <div><br></div> <div>우선, 입력을 받기 위해서 scanf를 쓸 때는,</div> <div>실제로 scanf가 입력을 제대로 받았는지 확인하는 것이 가장 기본일 것 같습니다.</div> <div><br></div> <div>scanf는 int형의 return 값이 있는데, format에 지정된 대로 값을 읽은 결과, <span style="font-size:9pt;line-height:1.5;">성공적으로 읽어서 변수에 저장한 갯수를 리턴합니다.</span></div> <div><br></div> <div>ret = scanf("%d%f%c", &v1, &v2, &v3);</div> <div><br></div> <div>위의 경우, scanf를 통해서 성공적으로 읽어 들인 값이 1개인지, 2개 또는 3개인지에 따라,</div> <div>ret의 값이 정해지게 됩니다. 물론, 하나도 못읽어들였다면 ret의 값은 0이 되겠죠.</div> <div>따라서, scanf의 리턴값과 v1/v2/v3 등의 변수의 값을 조사해서,</div> <div>적절한 예외 처리와 조치를 취하는 것이 좋을 것 같습니다.</div> <div><br></div> <div>그리고, 여러 번의 scanf 함수 호출로, 사용자로부터 입력을 받아야 할 경우에는,</div> <div>input buffer에 남아 있는 white space나 개행문자, 또는 필요없이 남아 있는 문자들을 잘 제거해 주는 것이 필요하고요,</div> <div>특히나, Windows/Dos 에서는 엔터키를 칠 때 0D0A 라는 두개의 캐릭터가 input stream buffer에 들어가기 때문에,</div> <div>0A가 남아서 다음번 scanf 입력에서 원치않는 결과를 초래하는 것을 잘 방지해야 한다고 봅니다.</div> <div><br></div> <div>그래서,</div> <div>10개의 정수를 입력받는 프로그램을 <span style="font-size:9pt;line-height:1.5;">scanf를 써서 구현할 때,</span></div> <div><span style="font-size:9pt;line-height:1.5;">가장 이식성이 좋게 만든다면,</span></div> <div><br></div> <div> <div class="colorscripter-code" style="color:#010101;overflow:auto;font-family:Consolas, 'Liberation Mono', Menlo, Courier, monospace;"> <table class="colorscripter-code-table" style="margin:0px;padding:0px;border:none;background-color:#fafafa;" cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:6px;border-right-width:2px;border-right-style:solid;border-right-color:#e5e5e5;"> <div style="margin:0px;padding:0px;text-align:right;color:#666666;line-height:130%;"> <div style="line-height:130%;">1</div> <div style="line-height:130%;">2</div> <div style="line-height:130%;">3</div> <div style="line-height:130%;">4</div> <div style="line-height:130%;">5</div> <div style="line-height:130%;">6</div> <div style="line-height:130%;">7</div> <div style="line-height:130%;">8</div> <div style="line-height:130%;">9</div> <div style="line-height:130%;">10</div> <div style="line-height:130%;">11</div> <div style="line-height:130%;">12</div> <div style="line-height:130%;">13</div> <div style="line-height:130%;">14</div> <div style="line-height:130%;">15</div> <div style="line-height:130%;">16</div> <div style="line-height:130%;">17</div> <div style="line-height:130%;">18</div> <div style="line-height:130%;">19</div> <div style="line-height:130%;">20</div> <div style="line-height:130%;">21</div> <div style="line-height:130%;">22</div> <div style="line-height:130%;">23</div> <div style="line-height:130%;">24</div> <div style="line-height:130%;">25</div> <div style="line-height:130%;">26</div> <div style="line-height:130%;">27</div> <div style="line-height:130%;">28</div> <div style="line-height:130%;">29</div> <div style="line-height:130%;">30</div> <div style="line-height:130%;">31</div></div></td> <td style="padding:6px 0px;"> <div style="margin:0px;padding:0px;color:#010101;line-height:130%;"> <div style="padding:0px 6px;white-space:pre;line-height:130%;"><span style="color:#0086b3;">#include</span> <span style="color:#a71d5d;"><</span>stdio.h<span style="color:#a71d5d;">></span></div> <div style="padding:0px 6px;white-space:pre;line-height:130%;background-color:#f0f0f0;"> </div> <div style="padding:0px 6px;white-space:pre;line-height:130%;"><span style="color:#066de2;">int</span> main()</div> <div style="padding:0px 6px;white-space:pre;line-height:130%;background-color:#f0f0f0;">{</div> <div style="padding:0px 6px;white-space:pre;line-height:130%;"> <span style="color:#066de2;">int</span> i;</div> <div style="padding:0px 6px;white-space:pre;line-height:130%;background-color:#f0f0f0;"> <span style="color:#066de2;">int</span> c;</div> <div style="padding:0px 6px;white-space:pre;line-height:130%;"> </div> <div style="padding:0px 6px;white-space:pre;line-height:130%;background-color:#f0f0f0;"> i <span style="color:#a71d5d;">=</span> <span style="color:#0099cc;">0</span>;</div> <div style="padding:0px 6px;white-space:pre;line-height:130%;"> <span style="color:#a71d5d;">do</span></div> <div style="padding:0px 6px;white-space:pre;line-height:130%;background-color:#f0f0f0;"> {</div> <div style="padding:0px 6px;white-space:pre;line-height:130%;"> <span style="color:#066de2;">printf</span>(<span style="color:#63a35c;">"Input Number[%d]: "</span>,i);</div> <div style="padding:0px 6px;white-space:pre;line-height:130%;background-color:#f0f0f0;"> </div> <div style="padding:0px 6px;white-space:pre;line-height:130%;"> <span style="color:#a71d5d;">switch</span>(<span style="color:#066de2;">scanf</span>(<span style="color:#63a35c;">"%d"</span>,&c))</div> <div style="padding:0px 6px;white-space:pre;line-height:130%;background-color:#f0f0f0;"> {</div> <div style="padding:0px 6px;white-space:pre;line-height:130%;"> <span style="color:#a71d5d;">case</span> <span style="color:#0099cc;">1</span>:</div> <div style="padding:0px 6px;white-space:pre;line-height:130%;background-color:#f0f0f0;"> <span style="color:#066de2;">printf</span>(<span style="color:#63a35c;">"Input: %d\n"</span>,c);</div> <div style="padding:0px 6px;white-space:pre;line-height:130%;"> i<span style="color:#a71d5d;">+</span><span style="color:#a71d5d;">+</span>;</div> <div style="padding:0px 6px;white-space:pre;line-height:130%;background-color:#f0f0f0;"> <span style="color:#a71d5d;">break</span>;</div> <div style="padding:0px 6px;white-space:pre;line-height:130%;"> <span style="color:#a71d5d;">default</span>:</div> <div style="padding:0px 6px;white-space:pre;line-height:130%;background-color:#f0f0f0;"> <span style="color:#066de2;">printf</span>(<span style="color:#63a35c;">"No Integer Found\n"</span>);</div> <div style="padding:0px 6px;white-space:pre;line-height:130%;"> <span style="color:#066de2;">printf</span>(<span style="color:#63a35c;">"Check Your Input: >>>"</span>);</div> <div style="padding:0px 6px;white-space:pre;line-height:130%;background-color:#f0f0f0;"> <span style="color:#a71d5d;">while</span>((c <span style="color:#a71d5d;">=</span> getchar()) <span style="color:#a71d5d;">!</span><span style="color:#a71d5d;">=</span> <span style="color:#63a35c;">'\n'</span> && c <span style="color:#a71d5d;">!</span><span style="color:#a71d5d;">=</span> <span style="color:#63a35c;">'\r'</span> && c <span style="color:#a71d5d;">!</span><span style="color:#a71d5d;">=</span> EOF) <span style="color:#066de2;">printf</span>(<span style="color:#63a35c;">"%c"</span>,c);</div> <div style="padding:0px 6px;white-space:pre;line-height:130%;"> <span style="color:#066de2;">printf</span>(<span style="color:#63a35c;">"<<<\n"</span>);</div> <div style="padding:0px 6px;white-space:pre;line-height:130%;background-color:#f0f0f0;"> <span style="color:#a71d5d;">break</span>;</div> <div style="padding:0px 6px;white-space:pre;line-height:130%;"> }</div> <div style="padding:0px 6px;white-space:pre;line-height:130%;background-color:#f0f0f0;"> }</div> <div style="padding:0px 6px;white-space:pre;line-height:130%;"> <span style="color:#a71d5d;">while</span>(i<span style="color:#a71d5d;"><</span><span style="color:#0099cc;">10</span>);</div> <div style="padding:0px 6px;white-space:pre;line-height:130%;background-color:#f0f0f0;"> </div> <div style="padding:0px 6px;white-space:pre;line-height:130%;"> <span style="color:#a71d5d;">return</span> <span style="color:#0099cc;">0</span>;</div> <div style="padding:0px 6px;white-space:pre;line-height:130%;background-color:#f0f0f0;">}</div> <div style="padding:0px 6px;white-space:pre;line-height:130%;"> </div></div> <div style="text-align:right;margin-top:-13px;margin-right:5px;font-size:9px;font-style:italic;"><a target="_blank" href="http://colorscripter.com/info#e" target="_blank" style="color:#e5e5e5;text-decoration:none;">Colored by Color Scripter</a></div></td> <td style="vertical-align:bottom;padding:0px 2px 4px 0px;"><a target="_blank" href="http://colorscripter.com/info#e" target="_blank" style="text-decoration:none;color:#FFFFFF;"><span style="font-size:9px;padding:1px;background-color:#e5e5e5;">cs</span></a></td></tr></tbody></table></div></div> <div><br></div> <div>위와 같이, scanf의 리턴값을 조사해서 값을 제대로 읽어들이지 못했을 경우에는 input buffer를 비우도록 하는 것이 좋을 것 같습니다.</div> <div><br></div> <div><b>결론 (세줄 요약)</b></div> <div><b><br></b></div> <div><b>1. scanf를 여러 번 호출 할때, input buffer를 잘 지우자! (특히 Windows에서 0A 값이 남아서 문제가 자주 된다!)</b></div> <div><b>2. scanf는 입력 버퍼를 'SCAN' 하고, 원하는 형식의 값이 있으면 버퍼를 비우지만, 아니면 버퍼를 비우지 않고 그대로 놔둔다!</b></div> <div><b>3. scanf도 리턴 값이 있다. 리턴값을 잘 활용하자!</b></div> <div><br></div> <div>입니다... 그리고 참고로, fflush(stdin) 함수는 DOS에선 잘 동작하지만 유닉스 계열에선 동작하지 않는다는 것도요~</div> <div><br></div> <div>마지막!</div> <div>scanf 함수는 실제 업무에서는 '전혀' 쓰이지 않습니다... <strike>고로 지금까지 전 뻘짓한겁니다...ㅠㅠ</strike></div>
You never know what you're gonna get
댓글 분란 또는 분쟁 때문에 전체 댓글이 블라인드 처리되었습니다.