피씨컴의 기울어진 공관

● 공통 _s 가 붙는 함수군에 대하여

1. strDestination 버퍼를 넘어서는 메모리 복사를 방지하는 스트링 함수라 이해하면 되겠다.

2. 아래는 _s가 붙은것과 안 붙은것의 차이를 나타낸다.

char *pszBuf = new char[6];
strncpy( pszBuf,
"aaaaaaaa", 7 ); // --> (1)
strcpy_s( pszBuf, 7,
"aaaaaaa"); // --> (2)

(1) 의 코드는 할당된 메모리(6byte)를 넘어서서 7byte까지 'a'로 채운 후 다음 코드가 진행된다.

    - 메모리를 할당하거나 해제 할 때 문제가 발생 요지가 있다.

    - 운이 없다면 문제가 없는 것처럼 보일 수 도 있다.

 

(2)의 코드는 메모리 에러를 발생시키며, 프로그램이 종료된다

 

3. _s류 함수의 첫번째 인자로 배열이 들어가면 매크로에 의해 사이즈가 자동 계산된다.

 

● 문자열 연결

- char* strcat( char* pDest , const char* pSrc );

1.strcat 함수는 pDest 문자열 뒤에 pSrc 문자열을 덧붙인다.

2. dest의 끝에 있던 널 종료 문자 자리에 src 문자열이 추가된다.

3. dest의 끝에 있는 널 종료 문자는 삭제되지만 src문자열 끝에 있는 널 종료 문자가 같이 추가되

    므로 연결 후 dest는 완전한 문자열이 된다.

4. 배열의 끝 점검을 하지 않고 무조건 문자열을 연결하므로 dest는 합쳐질 문자열의 길이까지

    고려하여 충분한 크기를 가지고 있어야 한다. 그렇지 않으면 dest 뒤쪽의 메모리가 파괴된다.

5.첫번째 매개변수에 전달된 주소 값(pDest) 반환

- int strcat_s( char* pDest, unsigned int nDestByteSize, const char* pSrc );

 

 

● 문자열 비교

- int strcmp( const char* pDest, const char* pSrc );

1. 문자열 비교 함수는 두 문자열이 같은지 다른지, 다르다면 어떤 문자열이 더 큰지를 비교한다.

2. 두 문자열이 동일하면 0, 동일하지 않으면 0이 아닌 갑 반환

    음수를 리턴하면 앞서는 문자열이며, 양수를 리턴하면 뒤지는 문자열이다

 

pDest 과 pSrc가 같으면 0

pDest > pSrc 이면 양수

pDest <  pSrc 이면 음수

 

● 문자열 검색

- char *strchr(const char *string, int c);

1. 문자열중에 c라는 문자가 있는지를 찾아 그 포인터를 리턴한다.

2. 만약 지정된 문자가 발견되지 않으면 strchr 함수는 NULL을 리턴한다.

3. 배열의 0번째부터 순차적으로 검색해 나간다.

 

- char *strrchr(const char *string, int c);

1. 문자열중에 c라는 문자가 있는지를 찾아 그 포인터를 리턴한다.

2. 만약 지정된 문자가 발견되지 않으면 strchr 함수는 NULL을 리턴한다.

3. 배열의 마지막부터 순차적으로 검색해 나간다.

 

- char *strstr(const char *string, const char *strSearch);

1. 문자열에서 부분 문자열을 찾는다.

2. 부분 문자열을 구성하는 일련의 문자들이 연속적으로 발견될 때 그 시작 번지를 리턴한다.

 

- char *strpbrk(const char *string, const char *strCharSet );

1. 문자열 검색 함수 중에 가장 복잡하며 사용 빈도도 낮지만 잘 알아 두면 여러 번 검색해야 하는

    작업을 간단하게 끝낼 수 있다

2. 이 함수는 첫 번째 인수로 주어진 문자열에서 두 번째 인수로 주어진 문자열에 속해 있는 문자

    중 가장 먼저 발견된 문자를 찾아 그 번지를 리턴한다

 

- char *strtok(char *strToken, const char *strDelimit);

1, 문자열을 토큰으로 잘라낸다.  

2. 첫 번째 인수로 잘라낼 문자열을 주며 두 번째 인수로 구분자를 구성하는 문자열을 준다

3. 구분자는 한 문자열에 여러 개를 지정할 수 있다 /와 :그리고 ,가 토큰 구분자라면 "/:,"를 준다

4.  최초 호출될 때 문자열의 첫 번째 토큰을 찾고 두 번째 토큰 위치를 NULL문자로 만든 후 토큰

     의 포인터를 리턴한다.

5. 검색한 토큰을 널 종료 문자열로 만들어 주므로 strtok가 리턴하는 포인터를 바로 출력하거나

    별도의 버퍼에 복사하면 분리된 토큰을 얻을 수 있다

6. strtok 함수는 중간 검색 결과를 자신의 정적변수에 저장해 놓는데 검색을 계속 하려면 첫 번째

   인수를 NULL로 전달하면 된다

7. 더 이상 토큰이 발견되지 않으면 NULL을 리턴하므로 strtok(NULL, 구분자)를 반복적으로 호출

    하면 문자열을 구성하는 모든 토큰을 찾을 수 있다

 

 

 

 

● 문자열 복사

- char* strcpy( char* pDest , const char* pSrc );

1. 함수 이름 strcpy는 String Copy의 준말이므로 문자열 복사 함수

2. 두 개의 문자형 포인터를 인수로 취하는데 dest는 복사될 목적지이고 src는 복사될 원본

    즉, src 문자열이 dest로 복사되는 것.

3. src의 제일 끝에 있는 널 종료 문자도 같이 dest로 복사된다.

4. 리턴값으로는 dest의 번지가 다시 돌려지는데 특별한 의미는 없으며 잘 사용되지도 않는다.

5. 이 함수를 사용하면 문자 배열을 선언한 후 문자열 상수를 복사할 수 있으며 언제든지 문자열

    의 내용을 바꿀 수 있다.

 

- int copy_strcpy_s( char* pDest, unsigned int nDestByteSize, const char* pSrc )

1. 함수 이름 strcpy는 String Copy의 준말이므로 문자열 복사 함수

2. 두 개의 문자형 포인터를 인수로 취하는데 pDest는 복사될 목적지이고 pSrc 는 복사될 원본

    즉, pSrc 문자열이 pDest로 복사되는 것.

3. pSrc의 제일 끝에 있는 널 종료 문자도 같이 pDest로 복사된다.

4. 만약 문자열을 받을 곳과 넣고자 하는 문자열이 NULL Pointer라면(아무것도 없는것을 가르킨

    다면) 혹은 문자열을 받을 곳의 크기가 너무 작다면 파라미터 핸들러는 파라미터를 확인하라

    는 이유로 인식하지 못한다 만약 실행된다면 그 함수는 EINAL(유효하지 않다)를 리턴하고

    errno를 일어나게 한다. (즉 복사원본의 NULL보다 2번째 인자가 작으면 에러남 물론 NULL

    포함 해서)

5. 이 함수들의 디버그 버전은 0xFD를 버퍼에 쌓는다.

6. _s 를 붙이는 것은 stack overflow를 막기위해서이며 null문자 이후에 0xfd로 채워서

overflow를 막는다?

7. 인자를 두개만 쓸수도 있는데 첫번째 인자로 배열이 들어가게 되면 메크로에 의해 사이즈가 자

    동 계산 되어 들어가기 때문

 

● 문자열 변환

- char *strset(char *string, int c);

1. strset 함수는 문자열을 c문자로 가득 채운다. 널 문자 직전까지의 모든 문자들이 c 문자로 바

    뀐다.

2. 리턴 : src의 번지를 리턴

 

-char *strnset(char *string, int c, size_t count);

1. strnset 함수는 개수를 지정할 수 있다는 것만 다르다.

2. 개수를 지정한다고 해도 가장 우선되는건 널이다. "abc"문자열에 아무리 c로는 *을 count로 5

    를 줘봤자 변하는건 abc 만큼의 별 "***" 가 전부이다

3. 리턴 : src의 번지를 리턴

 

- char *strlwr(char *string);

1. 모든 문자를 소문자로 바꾼다.

2. 영문자가 아닌 한글이나 숫자, 기호는 그대로 유지한다,

3. 인수로 주어진 src를 그대로 리턴

 

char *strupr(char *string);

1. 모든 문자를 대문자로 바꾼다.

2. 영문자가 아닌 한글이나 숫자, 기호는 그대로 유지한다

3. 인수로 주어진 src를 그대로 리턴

 

- char *strrev(char *string);

1. strrev 함수는 문자열을 거꾸로 뒤집는다

2.널 종료 문자는 교환 대상에서 제외된다.

3.문자열을 구성하는 개별 문자를 기계적으로 교환할 뿐이므로 한글에 대해서는 제대

   동작하지 않는다.

 

● 문자 관리

- int isprint(int c);

1. 인쇄가능한 글자인지를 검사한다 즉 기능키인지를 검사한다.

2.  맞다면 0이 아닌 수를 리턴하며 아니라면 0을 리턴한다.

 

- int isgraph(int c);

1. 인쇄가능한 글자인지를 검사한다 즉 기능키인지를 검사한다.

2. 위 isprint랑 똑같지만 이녀석은 공백을 제외한 인쇄가능한 문자를 검색한다.

3.  맞다면 0이 아닌 수를 리턴하며 아니라면 0을 리턴한다.

 

- int ispunct(int c);

1. 구두문자인지 아닌지를 판별한다.

2. 구두문자라면 0이 아닌수를 리턴하고 구두문자가 아니라면 0을 리턴한다.

 

- int isspace(int c);

1. 공백인지 아닌지 검사한다.

2. 공백이라판별하는 것은 0x09~0x0d, 0x20 이다.

3. 공백이라면 0이 아닌수를 공백이 아니라면 0을 리턴 

 

 

● 문자 관리

- void* memcpy( void* pDest, const void* pSrc , unsigned int nSize );

1.  복사할 메모리공간의 값을 복사받을 메모리공간에 메모리 공간 크기 만큼 복사한다.

2.  nSize  부분엔 배열 쓰듯 원소 갯수를 쓰는게 아니고 메모리 바이트 단위를 넣는다

    int == 4byte

3. memcpy는 대응되는 바이트끼리 기계적으로 복사하기 때문에 복사할 대상의 논리적인 구조 따

   위는 전혀 개의치 않는다. 그래서 배열뿐만 아니라 2차원 배열이나 구조체 배열 따위도

    memcpy를 사용하면 얼마든지 복사할 수 있다. 또한 널 종료 문자같은 것도 인식하지 않기 때

    문에 중간에 0을 만나도 지정한 길이까지는 무조건 복사한다.

4. 복사받을 공간이 복사할 공간보다 작다면, 오버 플로우가 발생한다.

 

- void* memmove( void* pDest, const void* pSrc , unsigned int nSize );

1. 복사받을 메모리 공간에 복사할 메모리 공간을 복사할 크기만큼 복사한다.

2. 복사받을 공간이 복사할 공간보다 작다면, 오버 플로우가 발생한다.

 

 

- memcpy와 memmove의 차이점.

1. memcpy는 메모리를 직접 복사하고, memmove는 임시 공간에 저장한 후 판단하여, 다시 복사

    한다. 즉, memmove가 memcpy에 비해 안정성이 높다.

2. memcpy는, memmove보다 안정성이 떨어지지만, 임시공간을 거치지 않고 바로 복사하기 때문에, memmove보다 상대적으로 빠르다.

 

 

- void * memset(void *dest, int c, size_t count);

1. memset은 보통 선언과 동시에 초기화 하지 못한 배열을 초기화 해줄때, 혹은 실행중 특정값으로 바꾸고 싶을때 사용한다.

2. dest를 count 만큼 c로 채워주는 함수이다.

3. dest를 리턴한다?

 

 

 

 

● 문자열 카운팅

- unsigned int strlen( const char* pSrc );

1. strlen은 String Length의 약자이며 문자열의 시작 번지만 주면 이 번지에 들어 있는 문자열의

    길이를 조사한다.

2. 널 종료 문자는 문자열의 끝을 나타낼 뿐 문자열의 일부가 아니므로 개수에는 제외된다.

 

 

 

 

 

 

 

 

 

 

1.memset을 사용할 때 주의할 점
- 1Bytes 변수(char, unsigned char 등)를 제외한 변수를 초기화 할 때에는 0이외의 값으로 초기화를 절대 하지마라.
- new, malloc 등을 이용하여 동적으로 배열을 생성하는 변수가 있는 struct, class에서는 초기화할 때 조심해라.
- CString은 절대 memset으로 초기화를 하지마라.
- virtual function을 가지고 있는 struct, class에서는 절대 memset으로 초기화를 하지마라.

memset을 사용할 때 위 4가지 경우만 기억을 하고 있으면 문제없다. 반드시 기억하고 있어야 한다. 각각에 대해서 간단하게 살펴보도록 하자.

1.1 1Bytes 변수(char, unsigned char 등)를 제외한 변수를 초기화 할 때에는 0이외의 값으로 초기화를 절대 하지마라.

이는 지난 시간에 설명을 한 부분이다. 다시 살펴보면,
int n;
memset(&n, 1, sizeof(int));
으로 하면 Byte단위로 처리가 되어 n = [00000001000000010000000100000001] = 16843009의 원하지 않는 값으로 초기화가 된다는 것을 명심해두자. 1Byte의 변수를 제외하고는 0으로만 초기화를 하는데 이용하자.

1.2 new, malloc 등을 이용하여 동적으로 배열을 생성하는 변수가 있는 struct, class에서는 초기화할 때 조심해라.

문제가 되는 경우를 살펴보면,
struct A {
   int i;
   char* c;
};

void main()
{
   A a;
   a.c = new char[100];
   memset(&a, 0, sizeof(A));
   if(a.c != NULL) {
      delete[] a.c;
      a.c = NULL;
   }
}

여기서 sizeof(A)는 struct member alignment가 어떤 값이든 4(int i) + 4(char* c, address는 4) = 8Bytes가 된다. 그러므로 위의 소스는 동적으로 생성한 변수는 초기화가 되지 못하고, char* c가 NULL로 초기화가 됨으로써, 이전에 생성한 메모리는 메모리 누수가 발생하게 된다.

그러므로 위와 같이 동적으로 생성하는 경우 각각을 분리하여 초기화를 하여야겠다.
a.i = 0;
memset(a.c, 0, sizeof(char)*100);

1.3 CString은 절대 memset으로 초기화를 하지마라.

1.2와 같은 경우로 CString은 내부적으로 m_pchData 변수를 동적으로 생성하여 문자열을 저장한다. 이 변수에 대한 직접적인 접근은 private로 막혀있다. 그래서 CString, 또는 CString을 member variable을 가지고 있는 struct, class를 memset을 이용하여 초기화를 하지마라.

CString을 memset으로 초기화를 하면, 1.2와 같이 메모리 누수뿐만 아니라, run-time error도 발생을 한다. 나를 포함해서 이것 때문에 고생을 한 사람이 꽤 많다는 것을 기억해두자.

1.4 virtual function을 가지고 있는 struct, class에서는 절대 memset으로 초기화를 하지마라.

여기서 virtual은 run-time에 실행함수를 binding하는 역할을 하는 것이다.
한번 잊어버렸던 기억을 되살리는 의미로 예제를 살펴보면,

class A {
public:
   void fun() { printf("A::fun() "); }
};

class B: public A {
public:
   void fun() { printf("B::fun() "); }
};

void main()
{
   A* a = new B();
   a->fun();
}

위의 경우 “A::fun()”이 출력된다.
하지만 상속을 하여 재정의를 한다는 목적은 재정의를 한 함수가 호출되기를 바라기 때문일 것이다. 이때 아래와 같이 class A만 간단히 변경하여 virtual만 추가를 하면,
class A {
public:
   virtual void fun() { printf("A::fun() "); }
};

재정의한 함수가 실행이 되어 “B::fun()”이 출력이 된다. 이는 a->fun();가 실행이 될 때, 이 함수가 virtual이므로 a의 실제 instance(=new B)에 대응하는 실제 함수를 run-time으로 binding 되기 때문이다.

이정도로만 virtual 동작에 대해서 기억을 되살려보는 것으로 마무리를 하고, 다시 memset으로 넘어오면,

class A {
   int i;
   char* c;
   void fun();
};
을 sizeof(A)를 하면 4(int i)+4(char*c, address)=8Bytes로 member function은 영향을 주지 않는다. 하지만,
class A {
   int i;
   char* c;
   virtual void fun();
};
의 경우는 다르다. 위의 8Bytes외에 실제 fun()이 binding을 위한 실행함수 주소를 저장할 공간 4Bytes를 추가적으로 가지므로, sizeof(A)는 총 12Bytes가 된다.

이때
class A {
public:
   void fun() { printf("A::fun() "); }
};

class B: public A {
public:
   void fun() { printf("B::fun() "); }
};

void main()
{
   A* a = new B();
   memset(a, 0, sizeof(B));
   a->fun();
}
와 같이 memset을 이용하여 초기화를 하면, virtual function이 NULL영역으로 binding이 되어, a->fun();에서 run-time에 알 수 없는 에러가 발생한다는 것을 반드시 기억해두자.

memset은 매우 편리하고, 강력하면서도, 조심해서 사용해야 한다는 것을 기억해두자.

 

 

 

'프로그래밍 > 참고' 카테고리의 다른 글

printf, scanf  (0) 2013.02.27
#include , <stdio.h>, main  (0) 2013.02.27
Call by value, adrees, reference  (1) 2013.02.05
함수 기초  (0) 2013.02.04
함수  (0) 2013.02.03
Posted by 피씨컴