본문으로 바로가기

Day-7 포인터, 표준 입력 함수

category Programming/TIPS 17기 2017. 7. 20. 13:22

 

 

'ptr = ' 과 '*ptr = '의 차이점

 

'ptr = ' : 일반 변수로 사용된다. 포인터 변수의 값(가리키는 대상의 주소)이 변경된다.

'*ptr = ' : 자신이 가지고 있는 값을 주소로 인식한다. 포인터가 가리키는 대상의 값이 변경된다.

 

'ptr = ' 형태는 포인터 변수에 주소를 저장한다.

 

포인터 변수에 저장된 주소는 '포인터가 가리키는 대상 메모리'의 시작 주소를 의미한다.

포인터에 &를 사용하지 않고 직접 주소를 지정해도 된다.

short *ptr;

ptr = (short *)0x0000006C // 0X0000006C는 int형 데이터이다. 따라서 (short *)로 캐스팅 해주어야 한다.

 

'*ptr = ' 형태는 포인터가 가리키는 대상에 값을 저장한다.

 

'포인터가 가리키는 대상'의 값을 변경할 때는 ptr 변수 앞에 *(번지 지정)연산자를 추가하여 '*ptr = ' 과 같이 사용한다. 

short *ptr;

ptr = (short *)0x0000006C

*ptr = 0x0412 // 0x0000006C 번지에 0x0412(1042)값을 대입한다.

 

다른 함수에 선언된 지역 변수 사용하기

 

포인터를 사용하여 간접 주소 방식으로 값을 대입하는 이유는 모든 변수가 같은 함수에 선언되는 것은 아니기 때문이다.

일반 변수는 다른 함수에 있는 변수를 사용할 수 없다. 하지만 포인터 변수는 다른 함수에 선언된 변수의 값을 읽거나 변경 가능하다.

 

직접 주소 방식으로 다른 함수에 선언한 변수 사용하기

 

간접 주소 지정 방식(포인터)으로 다른 함수에 선언한 변수 사용하기

함수를 호출할 때 값을 몇개든지 넘겨줄 수 있지만 return문으로는 단 한개의 값만 반환 가능하다.

지역변수는 함수와 함수사이에 참조할 수 없다. 따라서 포인터를 사용해서 넘겨주는 값 모두 다 돌려줄 수 있다.

행위는 Test 함수에 있는데 값은 main함수의 tips가 바꾸는 포인터를 사용한다. 즉, 대상이 아직 정해지지 않고 행위만 있는 문법이다.

 ex) 나는 홍길동을 봤다.            나는 그를 봤다.

       나는 홍길동과 놀았다.         나는 그와 놀았다.

        그는 홍길동이다.

 

직접 주소 지정 방식으로 변수 값 교환하기

 

 

모두 after : start = 5, end = 96으로 예상 했겠지만 사실 그렇지 않다. 왜냐하면 Swap함수에서  temp 변수를 이용하여 a와 b값을 서로 바꾸고 있지만 a, b는  start, end의 매개변수(매개변수는 지역변수이다.)일 뿐 직접적으로 start, end의 값에 영향을 주지 않는다. 따라서 Swap함수의 매개변수인 a,b는 Swap함수가 종료되면 메모리에서 사라진다. a, b는 main함수와 전혀 상관이 없고 최종적으로는 a, b가 바뀌었을뿐이다.

 

간접 주소 지정 방식 (포인타)으로 변수 값 교환하기

 

 

 

이번에는 출력에서 berfore와 after의 start, end 값이 서로 바뀌었다. 포인터를 사용하였는데 Swap함수의 매개변수가 start, end의 주소값을 참조하고 있다. 즉, 행위는  Swap함수에 있지만 포인터가 가리키는 대상의 값이 바뀌어 start, end의 값이 바뀐다. 정리하면 대상은 없고 Swap이라는 함수는 행위만 정의 되어 있다. Swap함수는 호출될 때 대상를 정의하고 사용할때마다 대상은 바뀔 수 있다.

 

int temp = * pa;        // pa가 가리키는 대상에 가서 값을 가져온 후 temp에 저장한다.

*pa = *pb;               // pb가 가리키는 대상에 가서 값을 가져온 후 pa가 가리키는 대상에 값을 저장한다.

*pb = temp;             // temp에 저장된 값을 pb가 가리키는 대상의 값에 저장한다.

 

포인터를 사용할 때 자주 발생하는 실수들

 

실수 유형1 : 하나의 * 연산자를 누락하는 경우, 컴파일 시에 오류가 발생한다.

 

pa = *pb;

pa는 자기 자신을 가리킨다. type은 int pointer이다.

*pb는 *pb가 가리키는 대상의 값을 가리킨다. type은 pointer형이 아닌 int형이다.

 

실수 유형2 : 모든 * 연산자를 누락하는 경우, 컴파일 시에 오류 발생하지 않는다.

 

int temp = *pa;        

pa = pb                  //  pb에 저장된 주소가 pa에 복사되어 pa와 pb가 가리키는 주소가 같아진다.

*pb = temp;           

 

const 키워드로 주소 변경 실수 막기 (const 키워드는 의지 반영이다.)

 

const 키워드를 이용하여 피호출자에서 호출자로부터 전달받은 주소를 변경하는 실수를 방지할 수 있다.

포인터는 자기자신을 바꿀 수 있고 자기가 가리키는 대상을 바꿀 수 있다. const는 변경을 막는 것이기 때문에 const 키워드가 2개 붙을 수 있다.

위치를 조합하면 const 키워드를 사용해 포인터 변수를 세 가지 방법으로 선언 가능하다.

포인터 변수를 다룰 때 실수 할 확률이 높기 때문에 const 키워드를 적절하게 활용하면 실수로 인한 버그를 줄일 수 있다.

 

const int *p;            //  *p를 사용하여 대상의 값을 변경하여 번역할 때 오류가 발생한다. 참조만 할 수 있다.

int *const p;            //  p가 가지고 있는 주소를 변경하면 번역할 때 오류가 발생한다. p자체의 값 변경이 불가능 하다.

const int *const p;  //  p가 가지고 있는 주소를 바꾸거나 *p를 사용하여 대상의 값을 바꾸면 번역할 때 오류가 발생한다.

 

사용할 메모리의 범위를 기억하는 방법

 

1. 시작주소와 끝 주소를 기억하는 것

- 시작 주소와 끝 주소로 메모리 범위를 기억하려면 총 8바이트가 필요하다.

 

2. 시작 주소와 사용할 크기로 메모리 범위 기억하기

- 사용할 메모리 크기는 명령문에 포함되어 있기 때문에 자신이 사용할 메모리의 시작주소만 기억하면 된다.

- 포인터도 자신이 가리킬 대상에 대해 사용할 범위는 저장하지 않고 사용할 메모리의 시작 주소만 기억하면 된다.

 

 

포인터 변수의 주소 연산

 

short data = 0;

short *p = &data;

p = p + 1;    //  포인터 변수에 저장된 주소 값을 1만큼 증가시킨다.

 

포인터 변수에 저장된 주소도 일반 변수처럼 연산이 가능하다.

주소를 1만큼 증가시킨다는 의미가 일반 수학 연산과는 다르다.

포인터에서 +1의 의미는 그 다음 데이터의 주소를 의미한다.

포인터 주소 연산 : 포인터 변수가 가진 주소를 연산하면 자신이 가리키는 대상의 크기만큼 연산한다.

 

포인터가 가리키는 대상의 크기

 

포인터가 변수를 선언할 때 * 연산자 앞에 적는 자료형은 포인터 변수가 가리키는 대상의 크기를 의미한다.

포인터 변수가 가리키는 대상의 자료형과 포인터 변수 선언 시 적는 자료형을 동일하게 지정하는 것이 일반적이다.

 

포인터가 가리킬 수 있는 크기와 실제 대상의 크기가 다른 경우

 

포인터가 가리킬 수 있는 크기와 실제 대상의 크기가 다를 수 있다.

프로그래머가 의도적으로 두 크기를 다르게 사용하는 경우도 있다.

 

int형 변수에저장된 값을 1바이트 단위로 출력하기 -- Displacement adressing

- 직접 위치를 이동하며 시작값을 옮기는 방식이다.

 

 

 

int형 변수에 저장된 값을 1바이트 단위로 출력하기 -- Indexed adressing

위치를 이동하지 않고 시작값도 바뀌지 않는 방식이다.

 

대상의 크기가 정해져 있지 않은 void * 형 포인터

 

void 키워드 : 정해져 있지 않다. (void는 무한하다라고 말하기도 한다.)

포인터 변수가 가리키는 대상의 크기를 모를 때 void * 형을 사용한다.

사용할 메모리의 시작 주소만 알고 끝 주소를 모를 때 void * 형 포인터를 사용한다.

자신이 가리키는 대상의 크기를 모른다. 따라서 모든 대상을 기리킬 수 있다.

void *는 주소를 사용할 때 반드시 '사용할 크기'를 표기해야 한다.

 

int data = 0;

void *p = &data;

*p = 5;                 //  주소는 아는데 크기를 몰라 오류가 발생한다.

*(int *)p = 5;        //  캐스팅을 통해서 주소와 크기를 모두 알기 때문에 오류가 발생하지 않는다.

 

int data = 0x12345678

int *p = &data;

 

*(short *)p = 3; // 0x0003

즉 0x12340003으로 바뀐다.

 

 

void * 형 포인터 활용하기

 

void * 는 자신이 사용할 대상의 크기 지정을 잠시 미룰 수 있다는 장점을 가진다.

main 함수의 지역 변수의 주소를 받아 1을 대입하는 Myfunc 함수

 

포인터 사용시 대표적인 버그 (굉장히 위험하다)

 

int data = 5, k;

int *p;                // p에 쓰레기 값이 들어 있다.

포인터를 선언한 후 초기화 하지 않고 쓰레기 값이 있는 상황에서 사용하는 것은 매우 위험하다.

*p = data;         // p의 쓰리게 값에 따라 프로그램이 죽을 수 있다.

 

표준 입력 함수란?

 

표준 입력 장치 : 다양한 입력 장치 중에 사용하는 시스템이 가장 기본으로 생각하는 장치이다.

표준 입력 함수 : 표준 입력 장치로부터 데이터를 입력 받기 위해 제공되는 함수이다.

 

temp    :  일시적인 데이터를 저장, 단순한 저장이다.

buffer   : 속도가 다른 두 장치에서 데이터를 주고 받을 때 장치 속도 차이를 해결하기 위해 나온 것이다.

cash    : 버퍼에 특정 알고리즘이 부여된 것이다. (캐쉬가 버퍼의 상위개념이다.)

 

 

입력 값을 임시로 저장하는 표준 입력 버퍼

 

표준 입력 버퍼 : 특정 키를 누를 때까지 사용자 입력을 임시로 저장하는 메모리이다.

운영체가 표준 입출력을 사용하는 시스템을 위해 표준 입력 버퍼(메모리)를 제공한다.

사용자의 입력 완료 시점은 enter키로 인식한다. enter키를 입력한 순간에 입력 버퍼에서 한 개씩 사용된다.

즉, enter키를 쳐야지만 동작한다. 끝나는 시점은 표준입력버퍼에 데이터가 다 사라질때까지 작동한다.

 

입력 버퍼를 초기화하는 rewind 함수

 

stdin 포인터와 rewind 함수를 사용하여 표준 입력 버퍼에 남아 있는 입력 정보를 모두 지울 수 있다.

표준 입력 함수 호출 시에 사용자가 잘못된 값을 입력하여 입력 버퍼에 정보가 남아있는 경우 rewind 함수를 이용하면 다음 표준 입력 함수 호출시에 영향을 미치지 않는다.

 

문자 한 개를 입력 받는 getchar 함수

 

getchar 함수는 키보드(컴퓨터의 표준 입력 장치)로부터 문자 하나를 입력 받는 표준 입력 함수이다.

문자 한 개를 입력 받더라도 enter키를 눌러야만 작업이 완료된다.

 

getchar 함수를 사용할 때 주의할 점

 

getchar 함수를 사용할 때 주의할 점 - 해결방법1

 

사용자가 반드시 한 번에 한 개의 문자만 입력하고 enter키를 누른다고 가정해야만 한다.

getchar 함수를 한 번 더 호출하여 enter키 값을 제거한다.

사용자가 한 번에 한 개의 문자만을 입력하지 않으면 문제 해결이 불가능하다.

 

 

getchar 함수를 사용할 때 주의할 점 - 해결방법2

 

사용자가 입력한 한 개의 문자 외에 다른 문자들이 입력 버퍼에 남아서 생기는 문제를 근본적으로 해결해야 한다.

입력 버퍼를 초기화하여 남아있는 문자들을 모두 제거하여 문제를 해결해야 한다.

 

 

getc 함수

 

표준 입력 장치를 의미하는 stdin을 인자로 넘겨주면 하나의 문자를 입력한다.

파일 포인터를 인자로 넘겨주면 파일에서 1바이트 정보를 출력한다.

인자 없이 표준 입력 장치에서 입력을 받는 기능만 제공하는 것이 getchar 함수이다.

getc 함수를 이용하여 getchar 함수를 만든다.

 

문자열을 입력 받는 gets 함수

 

gets 함수는 입력 버퍼에 enter키가 남지 않는다.

gets 함수로 입력 받은 문자열에선 enter키를 입력한 위치에 NULL 문자 0이 추가된다.

지금은 gets가 사라졌다. 따라서 gets 대신 fgets와 gets_s를 사용해야 한다.

 

char str[8];

str[8]=7;                // 컴파일러가 잡아준다.

str[index]=7;         // 컴파일러가 잡아주지 못한다. index는 바뀔 수 있는 변수이다.

 

str[8];                    // null문자 빼고 7개 이하의 문자로 이루어진 문자열로 입력되어야 한다.

gets(str);                // 어떤 제한도 없기 때문에 범위를 막을 수 없다. 즉, gets 함수 내에서 오류가 발생한다. 프로그램이 죽을수도 있다.

gets_s                    // 함수 자체에서 str의 범위를 넘으면 함수 내에서 오류가 발생한다.

fgets(str, 8, stdin);  // 제한이 있다.

 

char str[8];

char temp[20];

gets(str);

printf("%s \n", str);

입력 : 012345678901

출력 : 012345678901 (release 모드)

변수가 순차적으로 메모리에 올라가기 때문에 temp영역을 침범해서 메모리에 저장된다.