Scanf

scanf는 주어진 문자열 스트림 소스에서 지정된 형식으로 데이터를 읽어내는 기능으로 C 프로그래밍 언어로부터 유래했으며 많은 프로그래밍 언어에 쓰이고 있다. 입력한 문자열의 특정한 배치를 위한 다양한 기능에서 사용되는 매개 변수를 통제하는 것이다. 기능들은 문자열을 나누는 것을 할 수 있다. 그리고, 적절한 데이터 타입들의 성질을 문자나 숫자로 표현하여 나타낸  것들 속에서 변환할 수 있다. 문자열 스캔 기능들은 종종 기준이 되는 라이브러리 속에서 제공된다.

scanf라는 용어는 C 라이브러리로부터 왔다. C 라이브러리는 대중화 된 타입의 기능이지만, 이러한 기능은 C 이전에, ALGOL 68 속의 readf 와 같은,다른 이름으로 사용되었다. Scanf format string 은 형식화된 입력을 제공하였고, 이것은 printf format string 에 의해서 보완되어, 형식화된 출력을 제공하였다. 이러한 간단한 기능들과 변함없는 구성방식을 제공하는 것은 더욱 세련되고, 변함없는 파서( 다른 프로그램이 처리할 수 있도록 여러 부분으로 분해하는 프로그램 ), 또는 견본 엔진들이 비교되지만, 많은 목적을 위해서는 충분한 것이다.

scanf의 기본 형태는 다음과 같다:
int scanf(const char *format, ...);

역사
Mike Lesk의 scanf를 포함한 휴대용 입출력 라이브러리는 Unix 버전 7 때 공식적으로 일부가 되었다.

사용법 
scanf는 C에서 비롯했는데, 표준 입력(종종 명령 줄 인터페이스)으로부터 숫자나 다른 입력한 데이터타입을 입력 받아 읽어낸다.

아래는 C 언어에서 각 줄의으로부터 언포맷된 10진 정수값 가변 숫자를 읽는 코드이다.

#include <stdio.h>

int main(void)
{
    int n;
    while (scanf("%d", &n))
        printf("%d\n", n);
    return 0;
}

들어오는 표준화된 입력 ( 종종  명령 줄 인터페이스 또는 비슷한 종류의 텍스트 UI [유저 인터페이스] ) 으로부터 수나 다른 데이터 타입을 읽는다. C 코드는 표준 입력 흐름과 각각의 것을 줄을 바꾸어 출력하는 것으로부터 형식화되지 않은 십진법( integers )의 변수를 읽는다.

#include <stdio.h>

int main(void)
{
    int n;

    while (scanf("%d", &n) == 1)
        printf("%d\n", n);
    return 0;
}

위 프로그램에서 실행 후, 다음과 같은 불규칙적인 공간의 정수 목록은
 456 123 789 456 12
 456 1
       2378
지속적으로 다음과 같이 나타날 것이다.
 456
 123
 789
 456
 12
 456
 1
 2378
만약 프로그래머가 단어를 출력한다면
#include <stdio.h>

int main(void)
{
    char word[20];

    if (scanf("%19s", word) == 1)
        puts(word);
    return 0;
}
어떤 데이터 타입을 프로그래머가 프로그램이 읽기를 원하든지, 변수는 ( 위의 &n 과 같은 ) 포인터가 메모리에 포인팅해야만 한다. 마찬가지로, 그 기능은 정확히 수행되지 않을 것이다. 왜냐하면 그것은 프로그래머가 변수를 입력하려고 시도하는 메모리 위치에 포인팅하는 것보다 잘못된 메모리 구역에 겹쳐 쓰여지는 것을 시도하기 때문이다.

마지막 예시 주소 연산자 &는 전달인자로 사용되지 않는다 : 단어로써는 문자열의 이름으로 주소로 평가되는 모든 컨텍스트에서 배열의 첫번째 요소에 대한 포인터와 동일하다. &word로 표현되는 동안 숫자상으로 같은 가치를 가지고 평가한다. 그리고, 의미론적으로, 그것은 전적으로 다른 의미이다. 그것은 그것의 요소 자체보다는 전체 주소열 안에서 표준이다. 이 사실은 scanf의 출력 문자열에 접근할 때 머리에 세겨 두는 것이 필요하다.

scanf는 표준 입출력으로부터 읽어내는 것만 지정한다. 많은 PHP와 같은 인터페이스를 이용한 프로그래밍 언어들은 sscanf와 fscanf와 같이 파생되고 있다. 하지만 scanf는 아니다.

포멧 문자열 사용법
scanf안에 있는 포맷 플레이스 홀더(빠져 있는 다른 것을 대신하는 기호나 텍스트의 일부)
는 더 또는 덜 printf구성과 같다. 그리고, 그것은 기능을 뒤바꾼다. printf의 구성처럼, POSIX 주소 필드 확장n$는 정의되었다.

그것들은 주로 프로그램이 보통 알려진 데이터를 읽도록 디자인 되지 않았기 때문에, 비록 scanf가 이런 것들에 접속할지라도 만약 명확하게 명시되었다면 포맷 문자열 안에서 거의 지속적이다. 이러한 예외는 하나 또는 여러 여백 문자열이다. 이것은 모든 입력되는 여백 문자열에서 혼란을 일으킨다. 거의 공통적으로 사용하는 플레이스 홀더는 다음과 같다.

%a : 소수(floating number)를 16진법 표기법으로 스캔한다.

%d : 10진법으로 표시된 정수를 스캔한다.

%i : 10진법으로 표시된 정수를 스캔한다. %d와 비슷하게, 하지만 수를 16진법으로써 0를 우선 시할 때, 0x와 8진법을 우선시하여 설명한다.예를 들어 문자열 031은 %d를 이용하면 31을 읽을 것이고, %i를 이용하면 25를 읽을 것이고, 읽힐 것이다. %hi안에 h표시는 short로의 변환과 hh의 char로 변환을 나타난다.

%u : 십진의 무부호 정수()를 스캔한다. 상대적으로, %hu 스캔은 무부호 짧은 문자열을 위한 것이고, %hhu 스캔은 무부호 문자열을 위한 것이다.

%f : 소수(floating number)를 일반적인 표기법에서 스캔한다.

%g, %G : 소수(floating number)를 일반적 표기법이나 지수 표기법으로 스캔한다. %g는 문자열이 몇 없는 경우에서 사용되고, %G는 더 큰 경우에 사용한다.

%x,%X : 정수를 무부호 16진수로 스캔한다.

%o : 정수를 8진수로 스캔한다.

%s : 문자열을 스캔한다. 이러한 스캔은 띄어쓰기에서 끝낸다. 널 문자(NULL)는 문자열의 마지막에 축적된다. 이것은 입력되는 문자열의 길이가 1이상이어야 한다는 것을 의미한다.

%c : 널(NULL) 문자가 추가되지 않은 문자를 스캔한다.

띄어쓰기(whilespace) : 띄어쓰기는 스캔하면서 0을 스캔할 수도 있지만, 띄어쓰기 문자열에 해당하는 수를 스캔할 수도 있다.

%lf : 범위가 큰 소수(double floating number)를 스캔한다.(long float)

%Lf : 범위가 더 큰 소수(long double floating number)를 스캔한다.(long long float)

%n : 아무것도 예상할 수 없다. 입력에서 현재까지 소비된 문자 수는 int에 대해 포인터여야 하는 다음 포인터를 통해 저장된다. 이것은 변환이 아니라 함수에서 반환하는 개수를 늘리지 않는다.
위의 예시들은 복합체에서 수 수식어와 함께 사용될 수 있다. 그리고 l, L과 같은 수식어들은 %기호와 문자 사이에 사용하여 long과 long long을 나타내 줄 수 있다. 그것들은 %기호와 문자 사이에 숫자를 집어 넣는 것을 할 수 있다. long 를 %기호 앞에 사용하면 문자의 수처럼 스캔된다. %기호 바로 뒤에 있는 *기호는 이 형식 지정자가 읽은 데이터가 변수에 저장되지 않음을 나타낸다. 이 삭제된 변수에 대해 형식 문자열 뒤에 인수가 포함되어서는 안 된다.

Printf에서 ff수식어는 scanf에 존재하지 않는다. 왜냐하면 입출력의 방식이 서로 다르기 때문이다.

C90 표준에는 ll와 hh수식어가 존재하지 않는다. 하지만 C99 표준에는 존재한다.

포멧 문자열의 예시는 다음과 같다.

”%7d%s %c%lf”

위의 포맷 문자열은 처음으로 7자리 10진 정수를 스캔한다. 그런 다음, 남아있는 문자열을 읽는다. 공간이나 줄바꿈 또는 텝(tab)이 찾아질 때까지, 그런 다음 소모한다 공백을 공백이 없고 문자가 있을 때까지, 그런 다음 문자를 소모한다. 그리고 마지막으로 남아있는 큰 소수(double)을 스캔한다. 그러므로, 탄탄한 프로그램은 scanf가 호출을 성공하고, 작동을 적절히 하는지를 확인해야 한다.만약 입력이 정확한 포맷으로 되지 않으면, 데이터는 계속해서 입력에 머무를 것이고, 새로운 입력을 읽기 전에 혼란이 올 것이다.

이것을 피해 선택 가능한 방법은 fgets를 사용하고, 읽을 문자열을 조사한다. 마지막 단계는 sscanf로 끝낼 수 있다. 많은 공백을 포함한 문자 a, e, f, g의 경우 많은 구현에서 대부분을 동일한 파서로 축소하도록 선택한다. Microsoft MSVCRT는 e, f, g 로 수행하는 반면 glibc는 4개 모두에서 수행한다.

취약성
scanf는 포맷 문자열 공격에 취약하다. 세심한 주의는 포맷 문지열이 포함하는 한계를 문자열과 문자열 크기를 통해 보장한다.  대부분의 경우 유저로부터 입력되는 문자열 사이즈는 scanf기능 실행되기 전까지 임의적이고 결정되지 않는다. 이것은 의미한다. 명확한 길이가 없는%s플레이스 홀더를 사용하는 것은 본질적으로 불안정하고, 버퍼 오버플로우(buffer overflows)를 부당하게 이용할 수 있다. 다른 가능성 있는 문제는 다이나믹 포맷 문자열에서 허용한다. 예를 들어, 포맷 문자열들은 배열 파일 또는 유저의 파일을 저장한다. 이러한 경우, 허용된 길이가 정해지지 않은 문자열 입력은 명확할 수 없다. 포맷 문자열이 사전에 체크되지 않았다면, 그리고 한계가 실행된다. 이것과 관련해서는 포맷 플레이스 홀더가 추가되거나 대응하지 않는다. 이것은 실제 vararg list에 대응되지 않는다. 이러한 플레이스 홀더는 Stack 또는 원하지 않는 컨테이너 또는 심지어 불안정한 포인터에서도 varargs의 특정한 실행에 의존하여 특정하게 추출할 수 있다.

응용 
scanf을 이용한 계산기이다

출저 : hyhgo1님의 블로그#include <stdio.h>

int calculator( num1 , num2 , symbol ){
    if(symbol  == 1 ) {
        printf("%d와 %d을/를 더한 값은 %d입니다\n", num1, num2, num1+num2);
    } else if( symbol == 2 ) {
        printf("%d에서 %d을/를 뺀 값은 %d입니다\n", num1, num2, num1-num2);
    } else if( symbol == 3 ) {
        printf("%d와 %d을/를 곱한 값은 %d입니다\n", num1, num2, num1*num2);
    } else if( symbol == 4 ) {
        printf("%d에서 %d로 나눈 값은 %d입니다\n", num1, num2, num1/num2);
    } else if( symbol == 5 ) {
        printf("%d에서 %d로 나눈 후 나머지값은 %d입니다\n", num1, num2, num1%num2);
    } else {
        printf("잘못 입력하셨습니다.");
    }
    return 1;
}

int main() {
    int A;
    int N1;
    int N2;
    printf("계산할 기호는 \n1는 더하기\n2는 빼기\n3는 곱하기\n4는 나누기\n5는 나누기의 나머지\n");
    printf("계산할 기호를 입력하세요...\n");
    scanf("%d", &A);
    printf("첫번째 수를 입력하세요...\n");
    scanf("%d", &N1);
    printf("두번째 수를 입력하세요...\n");
    scanf("%d", &N2);
    calculator( N1 , N2 , A );
    printf("프로그램 종료");
    return 0;
}

같이 보기 
 printf
 C 프로그래밍 언어
 C++
 PHP
 형식 문자열 공격

외부 링크 
 

 C++ reference for std::scanf

Stdio.h