C Part6. 표준 입력 함수(rewind 함수, getchar, getc 함수, gets 함수), 문자열을 정수로 변환(atoi), 표준 입력함수 scanf, 배열과 포인터, 메모리 할당(코드, 데이터, 스택 세그먼트), 동적 메모리 할당(malloc, free)

14. 표준 입력 함수

표준 입력 함수

표준 입력 함수란?

다양한 입력 장치 중에 사용하는 시스템이 가장 기본으로 생각하는 장치를 ‘표준입력장치’ 라고 부른다. ex) 키보드


입력 버퍼를 초기화하는 rewind 함수
rewind(stdin); //rewind 함수를 사용하면 입력 버퍼를 초기화 할 수 있음

문자 한 개를 입력 받는 getchar, getc 함수
  • char 들어간건 무조건 한 글자
  • 두개의 차이점을 모르겠다. 그냥 한글자 받을 때 쓰는 함수들인듯 하다.

예제) getchar 함수를 사용할 때 주의할 점

void main(){
    int input_data;

    input_data = getchar();
    printf("first : %c\n", input_data);

    input_data = getchar();
    printf("second : %c\n", input_data);
}
//결과
//a
//first : a
//second :

엔터도 문자로 인식해서 버퍼를 초기화해주는 rewind(stdin)을 사용해줘야 한다.


문자열을 입력 받는 gets 함수
  • gets 함수는 한 번에 여러 개의 문자를 입력받을 수 있으며, 엔터 키를 입력할 때까지 모든 문자를 하나의 문자열로 간주한다.
  • 문자열을 저장하기 위해서 선언된 변수의 시작 주소를 넘겨줘야 한다.
void main(){
    char input_string[10];
    gets(input_string);   //gets를 안쓰고 input_string만쓰면 주소값만 출력된다.
    printf("input : %s", input_string);
}

ptr = &일반주소 ptr = 배열은 & 안써줘도 된다.

gets를 실행하고 나오는 값이 NULL(문자출력 X)이냐 NULL이 아니냐(문자출력)


void main(){
    char input_string[10];
    if(NULL != gets(input_string)){
        printf("input : %s", input_string);
    }else{
        printf("input -> cancled");
    }
}

위의 예제는 문자를 9개까지만 저장할 수 있다. 끝에 NULL값(엔터값)이기 때문에


문자열을 정수로 변환하기

gets 함수를 사용하면 1234같은 숫자를 입력해도 정수가 아닌 문자열로 인식한다. 그렇기 때문에 atoi라는 함수를 사용하여 문자열을 정수로 변환한다.

#include <stdlib.h> //atoi 함수를 사용하기 위해 포함
void main(){
    int first_num, second_num;
    char first_string[16], second_string[16];

    printf("input first number : ");
    gets("first_string");

    printf("input second number :");
    gets("second_string");

    first_num = atoi(first_string);
    second_num = atoi(second_string);

    printf("%d + %d = %d", first_num, second_num, first_num + second_num);
}

표준 입력함수 scanf

getchar 함수는 하나의 문자를 입력받는 함수이고 gets 함수는 문자열을 입력받는 함수이다. 그러나 scanf 함수는 문자, 문자열, 정수, 실수 모두 입력받을 수 있도록 형식화된 입력을 제공한다.

키워드 %d %hd %f %lf %c %s
입력 형식 정수 int 정수 short int 실수 float 실수 double 문자 문자열
int data;
scanf("%d", &data);
//scanf 함수의 기본 형태
void main(){
    int int_data;
    float float_data;

    scanf("%d", &int_data);
    scanf("%f", &float_data);

    printf("input : %d, %f", input_data, float_data);
}
  • scanf 함수 입력 값의 구별은 엔터나 공백(space)문자로도 가능하다.
  • 그리고 이 함수는 엔터키나 공백이 여러개 입력되면 무시하고 실제 정보를 기준으로 입력을 받아들인다. -> 아무리 엔터를 눌러도 skip한다.
  • 그렇기때문에 사용자가 입력하는 문자열에 공백이 포함된다면 gets함수를 사용하는것이 좋다.

scanf 함수는 입력 형식 키워드와 자료형이 일치해야 한다
char data1 = 5;
short data2 = 6;
int data3 = 7;
printf("%d %d %d", data1, data2, data3) //정상적으로 5,6,7 출력
scanf("%d %d %d", &data1, &data2, &data3); //번역할 때 오류는 없지만 실행할 때 문제 발생. (data1, data2가 자료형이 다름)
입력 형식 키워드 변수 자료형
%c char, unsigned char
%o, %d %x int, unsigned int
%f float
%lf double
%s char*, char[]

예제) scanf 함수를 사용하여 나이 입력 받기

void main(){
    int num = 0;

    while(1){   //무한루프
        printf("input age : ");

        if( scanf("%d", &num) == 0 ){   //숫자가 아닌 값을 입력하면 0
            rewind(stdin);
            printf("[Enter] digit number! \n");
        }else{
            if(num > 0 && num < 130){
                break;   //정상적으로 입력되었기 때문에 반복문을 빠져나감
            }else{
                printf("incorrect age!\n"); //나이의 범위가 잘못입력
            }
        }

    }
    printf("your age : %d", num); //입력된 나이 출력
}

15. 배열과 포인터

배열과 포인터 표기법

배열 표기법과 포인터 표기법의 관계

포인터는 포인터 변수가 가리키는 메모리의 시작 주소를 기준으로 삼고, 배열도 해당 배열이 사용하는 메모리 그룹의 시작 주소를 기준으로 삼는다.

따라서 배열과 포인터는 표기법을 서로 바꿔 사용할 수 있다.

void main(){
    //배열
    char data[5];
    data[1] = 5;      //아래의 *(data + 1) = 5; 는 해당 data[1] = 5;와 같음
    *(data + 1) = 5;  //위의 data[1] = 5;는 해당 *(data + 1) = 5;와 같음

    //포인터
    char data;
    char *p = &data; //data 변수의 주소를 p에 저장
    *p = 3; //p가 가리키는data 변수에 3을 대입. 아래줄과 같음
    p[0] = 3;  //*p = 3;과 같음. 위줄과 같음
}

책 369페이지 하단 필기한 그림 참조

배열과 포인터로 사용하는 것 중 배열 표기법이 더 간단해 보일 수 있다. 하지만 어떤 문법이든 표기가 간단하다는 뜻은 표현에 제약이 있다는 뜻

  • 배열 표기법은 요소를 구성하는 모든 바이트 값을 한 번에 수정하기 때문에 제약있다.
  • 포인터 표기법은 형변환(casting)을 하여 제약 없음(포인터 표기법은 배열 항목의 크기와 상관없이 자유롭게 값을 수정 할 수 있다).
    ex)*(char *)(data + 1) = 0x22; //일시적으로 char *형으로 변환함

배열을 사용하는 포인터

포인터와 배열은 연산 횟수의 차이. 배열의 색인 작업도 연산이기 때문에 같은 요소를 반복적으로 사용하는 경우에 효율이 떨어진다.(훨씬 느리다)

예제) 배열 예제를 포인터 사용해서 바꾸기 (포인터를 사용하여 배열의 각 요소에 저장된 값 합산하기)

void main(){
    char data[5] = {1,2,3,4,5};
    int result = 0, i;
    char *p = data;

    for(i = 0; i < 5; i++){
        result = result + *p;
        p++;
    }
    printf("data 배열의 각 요소의 합은 %d입니다.", result);
}
// 결과 -> data 배열의 각 요소의 합은 15입니다.

배열을 기준으로 포인터와 합체하기

char *형 포인터 변수가 3개 필요하다면 char *p1, *p2, *p3; 등과 같이 선언해서 포인터 변수를 만들어야 하는데, 포인터가 100개 이상 필요시 불편함. 따라서 포인터 변수도 배열로 선언해서 사용할 수 있다.

void main(){
    char *p[5];   //char *p1, *p2, *p3, *p4, *p5;라고 선언한것과 같음
}

위와 같이 선언하면 포인터가 5개 선언된 것이기 때문에 p 배열의 크기는 20바이트(포인터의 크기는 4바이트 고정)이다.

void main(){
    char data[3][5];
    char (*p)[5]; //char[5] 크기의 대상을 가리킬 수 있는 포인터 선언. 가독성이 떨어져서 교수님도 잘 안씀
    p = data; //포인터 변수 p는 2차원 배열 data 변수의 시작 주소를 저장
    (*p)[1] = 3; // p가 가리키는 대상의 2번재 항목에 3을 대입. p[0][1];과 같음
    (*(p+1))[2] = 4; //p+1이 가리키는 대상의 3번재 항목에 4를 대입. p[1][2]=4;와 같음
    (*(p+2))[4] = 5;  //p+2가 가리키는 대상의 5번째 항목에 5를 대입. p[2][4]=5;와 같음
    
    //-------------------------------------------위는 교재 내용임

    (*p)[5]; //포인터와 배열이 결합해서 2차원됐음. 줄을 의미(가로)
    //p[0] -> p+0 줄로 이동
    //p[1] -> p+1 줄로 이동

    //-------------------------------------------위아래 교수님 내용

    (*p3)[5] // 한줄
    p3 + 1 //다음줄로 이동
    *(p3+1) + x //x 옆으로 이동. 옆으로 이동하고싶으면 별쳐주기
    *(*(p3+1)+x) //해당 값 가져오기. 별 더쳐주면 갑 가져오기    
}

16. 메모리 할당

프로그램과 프로세스

  • 프로그래머가 만든 프로그램 실행 파일을 프로그램이라고 부른다.
  • 운영체제가 실행파일의 명령들을 읽어서 메모리에 재구성하는것을 프로세스라고 한다.
  • 프로세스는 단순히 실행할 명령들로만이루어져 있는 것이 아니라 다음 그림처럼 여러 가지 정보나 사용자가 입력한 데이터를 기억하는 메모리 공간도 포함하고 있습니다. 이러한 공간을 세그먼트라고 한다. (세그먼트 = 정보를 기록하는 메모리 공간)

프로세스세그먼트의 집합으로 구성되어 있다.

  • 코드 세그먼트 : 컴파일러는 C 언어 소스를 기계어로 된 명령문으로 번역해서 실행 파일을 만든다. 실행 파일이 실행되어 프로세스가 만들어지면 이 기계어 명령들은 프로세스의 ‘코드 세그먼트’에 복사되어 프로그램 실행에 사용된다. (기계어 명령들이 저장되는 곳)
  • 데이터 세그먼트 : 프로그램이 시작해서 끝날 때까지 계속 사용되는 데이터는 ‘데이터 세그먼트’에 보관된다.(데이터 저장)
  • 스택 세그먼트 : ‘스택 세그먼트’는 프로그램 실행 중에 필요한 임시 데이터를 저장하는데 사용하는 메모리 영역이다. 스택 세그먼트는 지역 변수가 놓이는 스택과 동적으로 할당되는 메모리 공간인 힙으로 나뉩니다.

정적 메모리 할당은 일회성 상수같은거에 사용


정적으로 할당된 메모리를 관리하는법

skip


지역 변수와 스택

스택에 대하여

스택은 후입선출 정도만 알고 skip. 이런게 있다정도만. 초심자에게 어렵고 원래 많은 양인데 몇페이지로 될 게 아니다.


동적 메모리 할당 및 해제

정적 메모리 할당의 한계

값이 변할 때마다 수정해야하고, 메모리 크기 제한있다(일정부분밖에 못씀)


동적 메모리 할당

malloc 함수로 동적 메모리를 할당하며(포인터로 해줘야 한다), void * 형식으로 반환해준다. void *를 사용하면 형변환(casting)을 해줘야 한다.

malloc 함수는 메모리 할당에 실패하는 경우도 있다! 이런 경우 malloc함수는 할당된 메모리 주소 대신에 NULL을 반환한다. 따라서 malloc 함수가 메모리 할당에 실패하는 경우를 대비하여 다음과 같이 넘겨받은 주소가 NULL인지 체크하는것이 좋다

short *p = (short *)malloc(100);
if(p != NULL){
    //메모리 할당에 성공함. 이 시점부터 100바이트 메모리 사용 가능
}else{
    //메모리 할당에 실패
}

free함수로 할당된 메모리 해제하기(반드시 해줘야 한다!)

동적메모리에서 언제 어떻게 할당받고 -> malloc 언제 어떻게 해제해야 하는지 -> free(p) (p가 가지고 있는 주소에 할당된 메모리 해제) 두개는 셋트!

할당되지 않은 메모리를 해제하거나(malloc함수 선언 x), 정적으로 할당된 메모리를 해제하거나(malloc함수 선언 x), 할당된 메모리를 두 번 해제하는 경우 오류 발생


동적 메모리 사용하기

정적 메모리 할당을 사용했을 때 발생할 수 있는 문제점
int data_size = 3;
int data[data_size]; //배열의 요소 개수는 상수만 명시! 오류발생
malloc 함수는 메모리 할당 크기를 변수로 지정 할 수 있다
int data_size = 12;
int *p = (int *)malloc(data_size); //12바이트의 메모리가 동적 할당됨

#include <malloc.h>

void main(){
    int *p_num_list, count = 0, sum = 0, limit = 0, i;
    
    printf("사용할 최대 개수를 입력하세요 : ");
    scanf("%d", &limit);

    p_num_list = (int *)malloc(sizeof(int)*limit);  //사용자가 입력한 개수만큼 정수를 저장할 수 있는 메모리를 할당

    while(count < limit){
        printf("숫자를 입력하세요 (9999를 누르면 종료) : ");
        scanf("%d", p_num_list + count);
        if(*(p_num_list + count) == 9999){
            break;
        }
        count++;
    }

    for(i = 0; i < count; i++){
        if(i > 0){
            printf(" + ");
        }
        printf("%d", *(p_num_list + i));
        sum = sum + *(p_num_list + i);
    }

    printf(" = %d\n", sum);
    free(p_num_list);
}

Comments