티스토리 뷰

반응형

당신은 파릇파릇하게 프로그래밍을 배우고자 하는 학생이다.

설레는 마음을 가지고 Visual Studio를 설치하고 Hello World!를 출력하고 기쁨을 만끽하는 것도 잠깐,

내가 입력한 것을 출력하고자하는데, 도저히 배운 scanf로 작동을 안 한다. 대체 왜?

 

아니 남들 다 잘 쓰고 책에서도 분명 scanf를 쓰란다. 근데 왜 오류가 날까?

VC++은 scanf 함수를 위험한 함수라고 생각한다. 입력될 저장 공간보다 더 긴 입력이 있을 때 누가 책임져주냐 따지는 것이다.

사실 온전히 막을 방법은 없다. 다만 저 오류같은 경고가 표시되지 않도록 하는 방법은 있다.

 

 

1. 설명에서 나오는 방법

  너무나 간단하게 소스 코드 어딘가, scanf가 나오기 전에 '_CRT_SECURE_NO_WARNING'을 define한다.

#define _CRT_SECURE_NO_WARNINGS

  짜잔! 컴파일 오류가 사라졌다. 빌드, 실행까지 아무런 문제가 없다. Visual Studio에서 오류 설명이 시키는대로만 했는데도 오류가 해결되는 일은 굉장히 드물다는 것 또한 알고 있을 것이다. 그래도 이렇게 정직한 오류 설명 덕분에 쉽게 해결할 수 있다.

 

 

2. 경고를 비활성화하기

  1번보다 조금 더 구체적으로 경고 자체를 무시해버린다. 1번과 비슷한 방법으로 C4996 경고를 해제한다.

#pragma warning(disable: 4996)

  역시 추가된 것은 한 줄. 저 한 줄로 마음의 평화를 되찾을 수 있다.

  그렇다면 1번과 2번 사이에서 어떤 것을 골라야 할지, 저게 어떤 의미고 왜 필요한지 궁금할 것이다. '저 둘 중 아무거나 넣어야지' 하지말고 제발 끝까지 봐주시라.

 

왜 쓰는 걸까?

  앞서 말했듯 Visual C++ 컴파일러는 기존 scanf 함수를 위험하다고 판단하고 개발자에게 경고한다. 그렇다면 왜 위험한 것일까?

먼저 그냥 scanf를 사용하여 배열의 크기를 넘는 입력을 해보자.

별 것 없는 scanf, printf 쌍

  10개의 char를 담을 수 있는 input이라는 배열을 선언하고, 그 배열에 scanf로 입력을 넣을 것이다. 입력은 11글자의 'HelloWorld!'로 해보자.

실행..된다?

  10개만 담을 수 있는 공간에, 11글자도 아닌 NUL 문자까지 12글자가 들어가고 출력까지 완벽하게 됐다. 이런 와중에 Visual Studio는 비명을 내지른다.

Run-Time Check Failure #2

  'Run-Time Check Failure #2 - Stack around the variable '변수명' was corrupted'란다. 대충 발생하는 원인은 크기에 맞지 않은 데이터가 들어갔을 때 발생한다. input이란 배열이 저장된 메모리 구조를 떠올려보자.

??? input 0 input 1 input 2 input 3 input 4 input 5 input 6 input 7 input 8 input 9 ??? ???
??? H e l l o W o r l d ! \0

  input의 0번째 index부터 9번째 index를 지나 어딘가 모르는 영역의 두 공간까지 입력이 차지하게 되었다. 운영체제께서 보시기에 참 좋지 않은 상황이다. 고작 디버그용 프로그램이지만, 충분히 다른 프로그램의 실행에도 영향을 끼칠 수 있는 상황인 것이다.

  그렇다고 일어나지도 않을 일(위 경우에서 9자리 이하의 입력이 보장된다면)까지 걱정하면서 scanf를 쓰지 못하게 하는 것은 지나친 처사다. scanf_s라는 대체가 있지만, 혹, 이런 비표준함수로 초심자들을 꼬셔 Visual Studio만 사용하도록 할 모략일 것인가. 또, scanf_s를 사용하더라도, 올바른 사용방법을 따르는 것도 아니다.

 

올바른 scanf_s의 사용

  하도 위험하다, 안전하지 못하다, 넘어선다하니 s는 대충 safe의 약자라 짐작이 가는데, 왜 안전할까?

  우리가 원했던 scanf의 사용과 똑같이 적용한다면 조금도 더 안전하지 않다. scanf의 뒤에 언더스코어 s하나만 붙였을 뿐인데 어떻게 더 안전하겠는가? 다만 Visual C++에서 원하는 용법은 따로 있다.

#include <stdio.h>

int main() {
    char input[10];
    scanf_s("%s", input, sizeof(input));
    printf("%s\n", input);

    return 0;
}

  입력을 받으면서 입력할 수 있는 공간의 크기를 사전에 전달하는 방법으로 초과된 입력을 회피하는 것이 scanf_s의 핵심이다. 기존 표준 scanf는 어떤 값이 입력되든 개발자가 이를 인지할 수 있는 시점은 함수의 호출이 종료된 이후이다. 얼마나 입력된 것인지 알 방법이 없는 것이다. Visual C++ 개발자들은 초심자들이 만든 프로그램에서, 심심하면 오버플로우가 발생하니 이런 결단을 내렸을 수 있다. 그렇다고 이전 scanf처럼 문자열 변수 하나만 달랑 던져준다면 달라지는 것은 없으니, 이것만으로 안전하다고 생각할 수도 없다.

  scanf_s도 scanf와 같이 서식 문자열과 이후 입력받을 대상 변수의 주소를 인자로 받는다. 따라서, C의 빈약한 가변인자 지원 아래, 개발자가 안전한 입력을 위해 공간의 크기를 입력했다는 것을 보장하지 못한다. 따라서 scanf와 똑같은 인자만 넘겨준다면, 아무런 차이가 없는 것이다. 그렇다면 결국 어떻게 해결해야할까?

 

그렇다면?

  코딩이나 개발과 관련한 이런 저런 이야기들을 듣다 보면 Visual Studio의 빈약한 C언어 지원에 대한 이야기는 익히 들었을 것이다. 가변길이배열이 등장한지 10년이 넘었음에도 불구하고 아직까지 지원하지 않는 것을 봐도 답이 나온다. 2016년에 마지막으로 수정된 이 문서를 보라, 아직도 얘네는 떳떳하다.

 

  정말 안타깝게도 아주 대부분의 PC는 Windows를 사용하고 있고, WSL를 설치하거나 Linux 계열의 운영체제를 직접 설치하는 것 같은 어려운 작업을 굳이 해가면서 C언어만을 배우는 것조차도 여간 고생이 아닐 것이다. 어차피 이후 C++나 Java와 같은 언어로 갈아타는 게 정석처럼 여겨지는 테크트리인데, 의외로 Visual Studio가 C++은 제법 잘 지원하는 IDE에 속한다. Java는 어디서 작성하든 동일한 실행결과가 보장되어야 하는 게 가장 큰 장점인 언어이니, Windows가 문제될 부분은 C언어에만 한정되는 것이다.

 

 

  결국 어떤 방법으로 저 문제를, 또 앞으로 나타날 저런 문제를 해결할지를 결정할 때다.

 

- 나는 Windows에서만 공부할 것이고, 이후에도 Windows만 사용할 것이다.

  개발에 발을 들이며 절대 지켜질 수 없는 문장이겠지만, 앞으로도 Windows용 프로그램만 만든다고 생각했을 때, Windows에서 제공하는 전용 확장을 사용하는 것은 괜찮은 선택이다. 다만, scanf_s도 scanf와 똑같은 위험을 가지고 있고 비표준 확장함수에 대한 이해를 하면서 배워나가야 할 것이다.

 

- 언젠가 Linux도 쓰고 스타벅스에서 Mac도 사용하고 싶다.

  맨 처음 사용한 오류 회피를 사용하자. 아주 대부분의 경우에서, 표준함수를 정확히 이해하고 작성한 코드는 어떤 컴파일러가 컴파일하더라도 문제없이 작동할 것이다. 다만, GCC 또는 Clang과 같은 컴파일러에서 당연히 오류가 났을 코드조차 모르고 Visual Studio가 오류가 아니라고 하면 '그렇구나'하고 그런대로 받아들이는 자세는 다른 환경에서 대체 불가능한 끔찍한 오류들을 마주하게 될지 모른다.

 이때, 확장 기능을 비활성화하는 습관을 들이도록([프로젝트 속성]-[C/C++]-[언어]-[언어 확장 사용 안 함])하자.

언어 확장 사용 안 함 (/Za 옵션)

  경고의 회피는 1번과 2번 중 필요에 따라 사용하는 센스가 필요할 것이다. C4996 경고라는 것이 저 Unsafe CRT에 관련된 하나의 내용만을 포함하지 않고, deprecated POSIX 함수, Unsafe 표준 함수 등의 모든 오류를 포함하고 있다. 따라서, 되도록 1번을 사용하되, C4996에 해당하는 경고들이 scanf 뿐만 아닌 다른 부분에서도 발목잡고, 문제에 대한 충분한 이해를 했다면 2번을 사용하는 것도 문제되지 않는다.

 

- 나는 이런 고민을 하는 것조차 싫다.

  C++로 갈아타는 최고의 선택을 하면 된다. 앞서 말했듯 Visual Studio는 C++에 진심인 IDE다. 어차피 C언어 맛보기로 잠깐 배우고 뭔가 내 손으로 만들기 좋은 언어로 갈아타는 결말만이 존재하는 것이 당연하다. 나중에라도 C언어로 돌아올 때즈음에는 이런 포스트가 필요없게 느껴질 것이다.

 

 

마지막 희소식

  작성일(2020년 10월 13일)기준 Visual Studio 2019 버전 16.8의 베타버전에서 C 표준 설정이 가능해진다. 무려 C11, C17을 지원한다고 하니 당장 영향을 끼칠 것이 부담되는 경우가 아니라면 베타버전을 사용해보는 것도 좋은 선택일 것이다. 30년째 C89/90만을 지원하고 부분적인 C99로 생색을 내더니 이제서야 C11, C17를 지원하려고 Windows까지 업데이트를 준비중이다. 어떤 변화가 생길지는 몰라도, 그때면 이 포스트가 오히려 레거시 포스트가 될 것이다. 새 C 표준을 적용하는 방법은 여기를 참고하자.

반응형
댓글
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함