● 공통 _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 |