카테고리 없음

C언어 [C특강] strcpy 함수 이해하기

카카오블로거 2021. 12. 30. 19:12

문자형 포인터는 다음과 같이 선언할 수 있다.

  char *p;

이 포인터를 통해서 우리는

  char *p;

  char ch;

  p = &ch;

또는

  char *p;

  p = "Korea";

또는

  char *p;

  char string[100];

  p = string;

을 사용할 수 있다.

이제 이런 포인터를 가지고 strcpy() 함수를 사용해 보자. strcpy() 함수의 원형(prototype)은

다음과 같다.

char *strcpy( char *strDestination, const char *strSource );

 

매개변수:

  strDestination : 문자열을 입력 받을 버퍼의 포인터를 입력해야 한다.

  strSource : 복사할 문자열을 입력해야 한다.

여기서 strDestination은 데이터형이 char*인데, 이것은 매우 깊은 의미가 있다. char*라는 것은

함수 내부적으로 값을 변경하겠다는 뜻이고, const char*는 함수 내부에서는 값을 변경하지

않겠다는 뜻이다. 그러므로 우리는 그에 따라 어떤 형태의 포인터를 넘겨 줄지를 결정해야 하며,

char*형은 반드시 변경될 수 있는 메모리를 넘겨줘야 하고, const char*는 변경될 수 있는 메모리

또는 변경될 수 없는 메모리(상수 영역 메모리) 모두 가능하다. 그럼 순서대로 포인터를 넘겨 주면서

각각에 대하여 연구해 보자.

1. 문자형 변수의 번지를 넘겨 주는 경우.

  char ch;

  char *p;

  p = &ch;

  strcpy( p, "Korea" );  // 컴파일 에러는 발생하지 않는다.

 

   위 문장에서 p ch의 메모리 번지값을 갖고 있다. 그러므로 컴파일 에러는 발생하지 않는다.

   하지만 문제는 strcpy() 함수가 내부적으로 "Korea" 문자열을 복사하려고 할 때 발생한다. p가

   가리키는 번지에 첫 번째 문자인 'K'는 성공적으로 복사된다. 그리고, 두 번째 문자인 'o'는

   ch 변수의 메모리 번지 다음 번지가 되는데 이 곳은 잘못된 번지이다(의미상). 그러므로

   프로그램은 잘못된 번지를 참조하여, 다운되거나 또는 다른 변수의 영역을 침범하게 된다.

   위에서 의미상이라는 것은 ch 변수가 Visual C++에서는 4바이트 공간을 할당한 후 1바이트만

   사용하기 때문이며, Visual C++이 아닌 하드웨어(임베디드 등) 프로그램을 만들 때는 ch는

   1바이트 공간만 할당 받게 된다. 이 점은 하드웨어 관련된 임베디드 프로그래밍을 할 때 주의

   해야 한다. 만약 다음과 같이 변수가 선언되어 있다면,

 

  int  i = 5;

  char ch;

  char *p;

  p = &ch;

  strcpy( p, "Korea" );  // 컴파일 에러는 발생하지 않는다.

 

   변수는 스택 영역에 선언되기 때문에 다음과 같이 할당된다. ch 변수가 있고, 그 다음에

   i 변수가 위치한다.

   +------------------------------------------------+

   +  1바이트 (여분3바이트) |           4바이트             |

   +------------------------------------------------+

   +              ch               |               i                   |

   +------------------------------------------------+

 

   메모리 번지 순으로 보면, ch 번지 다음에 i 번지가 있기 때문에, 위의 strcpy() 함수는 정상적으로

   작동하게 된다. 참고로 ch는 1바이트형이지만 Visual C++ 내부적으로 4바이트를 할당해서 1바이트만

   사용하게 된다. 현재 i의 값은 5가 있는데, strcpy() 함수 호출 시 메모리가 1바이트 침범당하기 때문에

   i의 값을 출력하면 97이 나오게 된다. 이 것은 ch의 영역에 kore가 복사되고, i의 영역에 a가 복사되었기

   때문이다. 다음과 같이 결과를 출력해 보면,

 

  printf( p );

  printf( "\n" );
  printf( "%d", i );

 

   출력결과는 다음과 같다.

 

   korea

  97

 

   97은 문자로 'a'이니까, 위와 같이 연속된 메모리 공간에 문자열이 복사되었음을 확인할 수 있다.

   참고로 Windows 운영체제는 맨 뒤의 값을 낮은 주소(Little-Endian)에 저장하는 방식을 사용하기

   때문에 i 변수의 맨 앞 번지에 맨 두의 값이 저장됩니다. 만약 i의 값이 0x12345678이라면 i 변수가

   위치한 메모리에는 다음과 같이 거꾸로 값이 저장됩니다. 

   +---------------------------------------+

   +   0번지  |   1번지  |   2번지   |   3번지   |

   +---------------------------------------+

   +     78     |     56     |     34      |      12     |

   +---------------------------------------+

   그러므로 위의 경우에 'a'가 0번지에 복사되어 결국, i는 97이라는 값을 갖게 됩니다. 저 위의 경우 문자열이

   복사되기 전의 i 값과 복사된 후의 i값은 다음과 같습니다.

   <복사되기 전>

   +---------------------------------------+

   +   0번지  |   1번지  |   2번지   |   3번지   |

   +---------------------------------------+

   +      5     |       0     |      0      |      0       |        0은 NULL('\0')

   +---------------------------------------+

   <korea가 복사된 후>

   +---------------------------------------+

   +   0번지  |   1번지  |   2번지   |   3번지   |

   +---------------------------------------+

   +     'a'    |       0     |      0      |      0       |        'a' 는 97(0x61)

   +---------------------------------------+

    이 값을 Little Endian 방식으로 표현하면, 0x00000061이 되므로 십진수로 표현하면 97이 됩니다.

2. 포인터를 초기화하지 않고 넘겨 주는 경우.

  char *p;  // 초기화를 하지 않으면 기본값이 0xCCCCCCCC

  strcpy( p, "Korea" );  // 컴파일 에러는 발생하지 않는다.

  

  위에 선언된 포인터 변수 p는 strcpy() 함수에 넘겨 줄 수 있다. 왜냐하면 strcpy() 함수는 char*를

  넘겨 받기만 하면 되기 때문이다. 여기서 문제는 p가 초기화되지 않았기 때문에 0xCCCCCCCC

  (일명 쓰레기값)으로 초기화 된다. 이 것은 'p가 0xCCCCCCCC의 번지를 접근하여 처리하기 위한

  포인터'가 된다는 것이다. 윈도우 운영체제는 특정 프로그램이 허가되지 않은 메모리 영역에 접근하는

   것을 원천 봉쇄하고 있다. 그것은 하나의 프로그램이 다른 메모리 번지에 접근해서 다른 프로그램에

   영향을 주는 것을 막기 위해서이다. 결국 위와 같은 문장은 문자 k를 p가 가리키는 메모리(0xCCCCCCCC)에

   복사하려고 할 때, 운영체제에 의해 저지당한다.

 

3. 포인터를 문자열 상수로 초기화한 경우.

  char *p = "대한민국";  // p는 "대한민국"을 가리키는 포인터

  const char *p = "대한민국";  // 정확한 선언

   // 위 선언은 const char *p = "대한민국"; 이라고 해야 정확하다. 왜냐하면

   // 문자열이 const char* 형이기 때문이다. 참고로 모든 const 형은 값을 참조할 수만 있고 변경할 수는 없다.

  strcpy( p, "Korea" );   // 컴파일 에러는 발생하지 않는다.

  

  strcpy() 함수는 첫 번째 매개 변수로 그 번지의 값을 마음대로 바꿀 수 있는 char*형을 원하고 있다. 그런데,

  이 곳에 const char*형을 건네주면, 그 값을 바꿀 수 없기 때문에 문법적으로 에러가 발생한다. 그리고 실제

  로 프로그램을 수행하면, 프로그램은 즉시 다운된다. 즉 const char*형을 char *형에 대입하면 컴파일 단계에

  서는 문법적으로 문제가 없지만, 프로그램 실행 시에는 심각한 문제가 된다.

 

 

4. 문자열 상수를 직접 사용한 경우.

  strcpy( "이 곳에 복사해 주삼", "Korea" );   // 컴파일 에러는 발생하지 않는다.

  

  strcpy() 함수는 첫 번째 매개 변수로 그 번지의 값을 마음대로 바꿀 수 있는 char*형을 원하고 있다. 그런데,

  위에 사용된 첫 번째 매개 변수는 const char*형을 건네 준다. 이 것은 서로 다른 성격의 포인터를 사용하

  는 것이 되므로 위법이지만, 컴파일러는 그냥 무시하고 건네 준다.

 

 

5. NULL을 사용하는 경우.

 

  char buff[100];

  strcpy( NULL, "Korea" );   // 컴파일 에러는 발생하지 않는다.

    strcpy() 함수는 첫 번째 매개 변수로 그 번지의 값을 마음대로 바꿀 수 있는 char*형을 원하고 있다.

   그런데, 지금 넘겨준 것은 NULL(0 번지)이고 strcpy() 함수는 내부적으로 0번지에 접근을 하려고

   시도하기 때문에, 프로그램은 다운된다.

  char buff[100];

  strcpy( buff, NULL );   // 컴파일 에러는 발생하지 않는다.

   위 코드의 두 번째 매개 변수가 NULL일 때 컴파일 에러는 발생하지 않는다. 하지만 strcpy() 함수 내부에서

   0번지(NULL)에 접근할 때, 접근할 수 없는 번지에 접근했기 때문에 프로그램은 다운된다.

결과적으로 strcpy() 함수를 사용하기 위해서 정확한 표현은 다음과 같다.

  char buff[100];

  char str[] = "Japan";

  char *p = str;

  strcpy( buff, "Korea" );

  strcpy( buff, str );

  strcpy( str, "Korea" );

  strcpy( str, buff );

여기서 명심해야 할 것은 첫 번째 매개 변수에 넘겨주는 메모리 번지는 strcpy() 함수에 의해 접근이 될 수

있는 번지여야 한다는 것입니다.