본문으로 바로가기

파일의 IT 블로그

  1. Home
  2. 프로그래밍/C
  3. [C] 메모리 관련 함수 memset(), memcpy(), memmove(), memcmp(), memchr() 알아보기

[C] 메모리 관련 함수 memset(), memcpy(), memmove(), memcmp(), memchr() 알아보기

· 댓글개 · KRFile

 

안녕하세요 파일입니다. 이번 UNIX 시스템 프로그래밍 과목을 수강하면서 따로 배우지 않고 넘어갔던 메모리 관련 함수 memcpy() 를 만나게 되었습니다. 메모리 함수에 대해선 따로 학습하지 않아서 적잖게 당황을 했는데 그런 김에 메모리 관련 함수의 레퍼런스를 정리해보면 좋을 거 같다는 생각이 들어서 본 글을 작성하게 됐습니다.

 

그래서 오늘 알아볼 함수들은 memset(), memcpy(), memmove(), memcmp(), memchr() 이 되겠습니다.

본 함수들은 메모리에 지정된 바이트 만큼 접근하는 함수로써 C언어를 전부 공부하시고 데이터가 메모리에 어떻게 저장되고 있는지 제대로 이해하신 후, 추가로 학습하시는걸 추천드립니다. 

 

* 참고로 본 글 예제는 대부분 Copilot AI로 생성한 것입니다. 진짜 편하긴 하네요

 

 

memset()

memset이라는 함수는 메모리의 내용(값)을 특정 값으로 설정할 수 있는 함수 입니다. 함수 이름 그대로 memory set(setting) 입니다.

 

#include <string.h> // #include<memory.h> 도 가능

void * memset ( void * ptr, int value, size_t num );

memset() 의 함수 원형은 위와 같으며 사용하기 위해선 <string.h> 헤더 파일이나 <memory.h> 헤더 파일을 include 해주시면 됩니다. 두 곳다 memset 이 정의되어 있어서 무엇을 쓰던 무방합니다.

 

첫 번째 인자 ptr은 값을 쓰고 싶은 메모리의 시작주소를 의미 합니다. void형 포인터라 어떤 형의 데이터를 가리키던지 무관합니다.

 

두 번째 인자 value는 메모리에 세팅하고자 하는 값입니다. int형이라 숫자만 넣어야 되나 생각할 수 있겠지만 내부적으로 'unsigned char'(0~255) 로 변환되어 저장되기 때문에 'a' 와 같은 문자를 넣어도 괜찮습니다. 

 

세 번째 인자 num은 몇 바이트 만큼 메모리를 쓸 지 정하는 부분입니다.

 

그래서 위 memset 함수를 한 줄로 요약해보면 다음과 같습니다.

=> ptr로 시작하는 메모리 주소로 부터 num개의 바이트를 value 값으로 채운다.

:: 또한 memset 함수는 1바이트 단위로 값을 초기화 해서 0과 char값을 이용한 초기화만 가능합니다. 이에 대해선 아래 예제서 추가 설명을 드리겠습니다.

 

memset() 예제

 

// memset 예제
#include <stdio.h>

int main()
{
    char str[] = "I hate Programming";
    memset(str, '-', 1 * sizeof(char));
    printf("%s", str);
}

- hate Programming

다음은 memset() 을 활용하는 예제입니다.

str에 뒤에 문자열 "I hate Programming" 을 저장했습니다. (문자열이므로 자동으로 뒤에 널문자 '\0' 까지 추가된 상태입니다.)

 

그리고 memset(str, '-', 1); 을 통해 호출하고 printf()로 str 을 출력해본 결과 출력 결과는 "- hate Programming" 이 나왔습니다.

 

기본적으로 배열의 이름은 배열의 시작주소 (배열의 첫번째 요소) 를 가리키고 있기 때문에 memset의 첫번째 요소로써 str을 주었고 , 2번째 요소로 쓸 문자인 '-' 를 주었습니다.

 

마지막으로 str은 char 배열로써 각 요소가 char이라 한 문자당 1바이트를 차지하게 되는데 맨 앞글자에 '-' 를 쓰기 위해 1바이트 즉, 정확히 계산하여 1 * sizeof(char) 을 주었습니다.

 

즉 쉽게 말해서 첫 번째 위치(글자)로 가서 문자 '-' 를 1바이트만큼 써라! 가 되겠네요.

 

배열은 기본적으로 1차원 이던, 2차원 이던, 3차원 이던 1차원 배열의 형태연속적인 메모리 공간으로 할당되어 나열되기 때문에 (컴퓨터 메모리 자체가 애초에 평면이기에 그렇습니다) 이렇게 바이트 단위로 쓰기를 진행해 값을 변경할 수 있는 겁니다.

 

// memset 예제
#include <stdio.h>
#include <memory.h>

int main()
{
    char str[] = "I hate Programming";
    memset(str + 2, '-', 4);
    printf("%s", str);
    return 0;
}

만약에 저 "I hate Programming" 이라는 문자열에서 hate 라는 부분만 지우고 싶다면, h 부분으로 포인터를 옮긴다음에 4글자 => char 4개이므로 4바이트만큼 지워주시면 됩니다.

 

(시작 위치를 str 에 2를 더해서 포인터 연산을 통해 2바이트 만큼 옮겼습니다.)

 

포인터에 대한 이해만 있으면 쉽게 수행할 수 있습니다!

 

// memset 예제
#include <stdio.h>
#include <memory.h>

// 배열 요소를 출력하는 함수
void print_array(int *arr, int size)
{
    for (int i = 0; i < size; i++)
        printf("%d ", arr[i]);
}

// 100개짜리 배열 생성 후 memset 으로 초기화
int main()
{
    int arr[100];
    memset(arr, 0, sizeof(arr));
    print_array(arr, 100);
    return 0;
}

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
0 0 0

배열의 초기값을 한꺼번에 지정해줄 경우 for문으로 반복을 돌면서 초기화 해주는게 아닌 다음과 같이 memset을 이용해서 초기화를 해줄 수 있습니다. 이것 역시 배열의 메모리가 연속적이기에 가능한 행동이 되겠습니다.

 

arr의 요소가 int형 변수 100개 이므로 일반적으로 int는 4바이트로 계산되어, 400바이트(4 x 100) 만큼 연속적으로 0으로 쓰기를 진행하고 있습니다.

 

// memset 예제
#include <stdio.h>
#include <memory.h>

// 배열 요소를 출력하는 함수
void print_array(int *arr, int size)
{
    for (int i = 0; i < size; i++)
        printf("%d ", arr[i]);
}

// 100개짜리 배열 생성 후 memset 으로 초기화
int main()
{
    int arr[100];
    memset(arr, 1, sizeof(arr)); //No!!
    print_array(arr, 100);
    return 0;
}

16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 
16843009 16843009 16843009

그러나 아까도 잠깐 언급했듯이 memset() 함수의 경우엔 1바이트 단위로 값을 초기화 하기 때문에 기본적으로 1바이트로 다뤄지는 char이나 0이 아니면 (1바이트씩 0으로 다 채워도 0이기 때문) 제대로 값을 초기화 할 수 없습니다.

 

1바이트 단위로 1이라는 값을 써서 표현했기에, 4바이트로 표현된 int형 숫자 1은 제대로 된 숫자로 표현될 수 없게 된 겁니다.

 

그렇기에 memset은 보통 문자열(char 배열)에서 문자들만 변경하거나, 숫자 배열을 0으로 초기화 해주는 역할로 사용하는 경우가 많습니다. 물론 배열을 전부 0으로 초기화 해주는 코드의 경우 int arr[100] = {0, }; 와 같은 한 줄 짜리 코드가 있어서 조금 안습이긴 합니다 -.-;

 

직접 메모리 주소로 찾아가서 (포인터로 찾아가서) 바이트 단위로 값을 써야할 상황이 있을때 사용하도록 합시다.

아마 대부분은 Low-Level 한 작업에서 사용하게 될 것입니다.

 

memcpy()

memcpy의 경우 'memory copy' 를 뜻하는 것으로 이름 그대로 메모리의 값을 복사하는 기능을 합니다.

 

#include <string.h> //C++의 경우 #include <cstring>

void* memcpy (void* dest, const void* source, size_t num)

원형은 위와 같으며 C언어는 <string.h> , C++ 의 경우 <cstring> 을 include 해주면 됩니다.

* C++ 에서는 .h 를 적어주지 않고 사용합니다.

 

첫 번째 인자 dest는 복사 받을 대상(destination - 도착지) 메모리의 포인터를 의미 합니다.

 

두 번째 인자 source는 복사할 원본(source - 원천) 메모리의 포인터를 의미합니다.

 

세 번째 인자 num은 몇 바이트 만큼 복사할 지 정하는 부분입니다.

 

=> memcpy 함수는 source 에 있는 원본을 num 바이트 만큼 복사해서 dest에 붙여넣는 함수입니다.

:: memcpy(목적지, 원본, 길이) 로 요약해볼 수 있겠습니다.

 

! 주의할 점 1

C언어에서 문자열 복사 시 끝에 문자열의 끝을 알려주는 널 문자('\0') 를 고려해서 추가적으로 +1byte 만큼 복사해주셔야 합니다.

 

! 주의할 점 2

memcpy는 source 메모리 블록과 dest 메모리 블럭이 겹쳐 있는 곳에는 사용하지 못합니다. 즉 원본 메모리와 붙여넣기 할 대상 메모리가 겹쳐져 있으면 함수가 제대로 작동하지 않습니다. 이런 경우에는 memmove() 함수를 이용합니다.

=> 그러나 요즘엔 memcpy, memmove 둘 다 동일하게 작동하긴 합니다.

 

memcpy() 예제

// int형 배열 dest에 int형 배열 src의 값을 memcpy 로 복사하는 코드

#include <stdio.h>
#include <string.h>

int main()
{
    int src[5] = {1, 2, 3, 4, 5};
    int dest[5];

    memcpy(dest, src, sizeof(src)); // src의 값을 dest에 복사

    for (int i = 0; i < 5; i++)
    {
        printf("%d ", dest[i]);
    }
}

다음은 int 형 변수 src 에 있는 값들을 dest로 통째로 복사하는 예제 입니다.

복사를 수행하는 부분은 memcpy(dest, src, sizeof(src)); 이 부분 입니다.

배열의 이름은 시작주소니깐 각 배열이름을 포인터 변수에 제공하면 되겠고, src의 크기 통째로 20바이트(4x5) 만큼 dest 에 쓰면 됩니다.

 

//배열을 인자로 받아서 출력하는 함수
void print_array(int *arr, int size)

//이렇게 작성해도 int arr [] 은 컴파일러에 의해 알아서 int *arr 로 변환됨
void print_array(int arr [], int size)

일반적으로 C언어에서 배열을 인자값으로 받는 경우엔 Call by Reference (참조, 주소에 의한 호출) 로 밖에 호출이 안돼서 보통 배열을 인자값으로 넘기면 시작 주소만 복사해서 포인터로 사용하지, 배열 내용을 통째로 복사할 수는 없습니다.

 

배열을 인자로 받을때 int * arr 이 아닌 int arr [] 로 써줄 수 있긴 한데 기본적으로 컴파일러가 int * arr을 int arr [] 로 자동 변환합니다. (내용을 통째로 복사하지 않고 주소만 복사하게 강제해놓은건 당연히 시스템 성능, 효율을 위해서 입니다.)

 

그렇기에 C언어에서는 배열을 인자로 넘길때 얕은 복사(Shallow Copy) 로써 밖에 사용할 수 없는데 이제 memcpy() 를 사용하면 배열 요소를 통째로 복사할 수 있는 깊은 복사(Deep Copy) 를 구현할 수 있게 됩니다!

 

int main()
{
    int src[5] = {1, 2, 3, 4, 5};
    int dest[5] = {0, 0, 0, 0, 0};

    memcpy(dest, src, sizeof(int) * 3); // src의 값을 dest에 복사 (앞의 3개만)

    for (int i = 0; i < 5; i++)
    {
        printf("%d ", dest[i]);
    }
}

1 2 3 0 0

추가적으로 바이트 단위로 복사하는 것이기 때문에 이렇게 일부 바이트만 복사해서 앞의 3개만 복사하는 것도 가능합니다.

 

// int형 배열 dest에 int형 배열 src의 값을 memcpy 로 복사하는 코드

#include <stdio.h>
#include <string.h>

int main()
{
    char src[] = "Hello";
    char dest[20];

    memcpy(dest, src, sizeof(char) * 4);

    printf("%s", dest);
}

Hella

또한 문자열 복사시 끝의 널문자를 간과하지 않으셔야 합니다.

본 예제의 경우 src에 4바이트 만큼 Hell 라는 문자열을 복사해서 dest에 넣어주고, %s로 문자열 출력을 했으나 Hella 라는 이상한 문자열이 출력된 예제입니다.

 

이 이유는 단순히 앞의 4글자, 4바이트만을 복사 했기 때문에 dest 끝에는 널문자가 존재하지 않기에 발생합니다.

(사실 문자열을 복사할 때 유사하게 사용할 수 있는건 strncpy 함수인데 이 녀석 역시도 끝에 널문자를 자동으로 넣어주지 않습니다 -.-) 

 

이렇게 문자열 일부 복사시 널 문자를 항상 고려해줍시다.

C++ string 쓰면 이런거 안해도 되는데

 

// int형 배열 dest에 int형 배열 src의 값을 memcpy 로 복사하는 코드

#include <stdio.h>
#include <string.h>

int main()
{
    char src[] = "Hello";
    char dest[20];

    memcpy(dest, src, sizeof(char) * 4);
    dest[4] = '\0';

    printf("%s", dest);
}

Hell

지금의 경우엔 그냥 하드 코딩으로 널문자를 넣었는데 여러분들은 모두 똑똑하시니 더 좋은 솔루션도 있겠죠! 잘 생각해보시길 바랍니다 ^^

 

 

 

memmove()

memmove 는 'memory move' 를 의미하는 것으로써 이름 그대로 메모리를 이동하는 역할을 합니다.

 

#include <string.h> //C++의 경우 #include <cstring>

void* memmove (void* dest, const void* src, size_t num);

함수 원형입니다. C언어는 <string.h> 를, C++는 <cstring> 을 include 해주시면 됩니다.

아까 memcpy() 와 이름만 다르고 추가 해야 하는 헤더파일이나 인자값 형태가 모두 동일합니다.

 

복사나 이동이나 그게 그거지 뭐

 

첫 번째 인자 dest는 복사 받을 대상(destination - 도착지) 메모리의 포인터를 의미 합니다.

 

두 번째 인자 source는 복사할 원본(source - 원천) 메모리의 포인터를 의미합니다.

 

세 번째 인자 num은 몇 바이트 만큼 복사할 지 정하는 부분입니다.

 

memcpy() vs memmove()

 

분명 설명을 듣고 이상함을 느끼셨을 수 있는데 "이동이라고 해놓고 왜 복사 에요?" 라는 생각이 들 수 있습니다. 

move라서 잘라내고 붙여넣는 기능을 한다고 생각할 수 있는데 그렇지 않고 이것도 복사하는 역할을 합니다.

 

이 설명만 듣고는 memcpy() 와 같다고 느끼시겠습니다만 내부적으로 memcpy 는 임시 공간을 거치지 않고 바로 복사하고 memmove는 그것보다는 안전하게 임시 저장 공간(버퍼) 에 복사하고 해당 위치에 가서 버퍼에 복사 된 것을 붙여 넣는 식으로 동작이 구현되어 있습니다.

 

그렇기에 성능 비교시 임시 저장 공간을 거치지 않고 복사하는 memcpy 가 memmove 보다 성능이 더 좋습니다.

그리고 memmove는 memcpy와 다르게 임시 저장 공간을 거치기 때문에 안정성은 당연히 memmove 가 더 좋습니다.

 

정리해보자면

 

- memcpy() 는 메모리를 임시 공간을 거치지 않고 바로 복사

- memmove()는 메모리를 임시 공간에 저장 한 후 판단하여 다시 복사

- memmove()는 memcpy() 에 비해 안정성 높음

- memcpy() 는 memmove() 에 비해 속도가 빠름

 

=> 결론적으로 memmove 함수는 source 에 있는 원본을 num 바이트 만큼 복사해서 dest에 붙여넣는 함수입니다.

 

memmove() 예제

// memmove int 배열 복사 예제

#include <stdio.h>
#include <string.h>

int main()
{
    int src[5] = {1, 2, 3, 4, 5};
    int dest[5];

    memmove(dest, src, sizeof(src)); // src에서 src 배열 바이트 크기만큼 dest에 복사해라

    // 복사한 결과 확인
    for (int i = 0; i < 5; i++)
        printf("%d ", dest[i]);

    return 0;
}

1 2 3 4 5

int형 배열을 통째로 복사하는 예시입니다.

src에서 sizeof(src) - 즉 배열 전체 바이트 만큼 복사해서 dest에 붙여넣기를 수행합니다.

 

한 줄 짜리 코드로 for문 복사를 대체할 수 있는 간편한 코드입니다.

 

// memmove 복사

#include <stdio.h>
#include <string.h>

int main()
{
    char src[] = "I Love You"; //실제로 널문자까지 11글자
    char dest1[] = "123456789024244242";
    char dest2[] = "abcdefghijasdfasdfasdf";
    char dest3[] = "ABCDEFGHIJasdfsadfsadf";

    // case1 : src 글자 수 만큼만 복사 (널문자 복사 X)
    memmove(dest1, src, sizeof(char) * 10);

    // case2 : 널문자까지 src 통째로 복사 (널문자 복사 O)
    memmove(dest2, src, sizeof(src));

    // case3 : 일부 글자만 복사
    memmove(dest3, src, 6);

    // dest 출력
    printf("%s\n", dest1);
    printf("%s\n", dest2);
    printf("%s\n", dest3);
    return 0;
}

I Love You24244242        
I Love You
I LoveGHIJasdfsadfsadf

다음은 배열로 구현한 문자열을 복사하는 예시입니다.

C언어에서 문자열을 다룰 땐 널문자를 꼭 고려해야 합니다.

printf()에서 "%s" 서식 문자를 이용해 문자열을 출력할 수 있는데, 이때 문자열을 읽는 방식이 널문자를 만날때까지 읽기 때문입니다.

 

case1의 경우 각 글자 char이 1바이트, sizeof(char) * 10 을 이용해 10바이트, 즉 10글자 만큼 복사해서 dest1에 복사하는 예시입니다. 실제 src는 I Love You\0 라는 형태로 끝에 널문자까지 해서 11글자가 저장되어 있습니다.

dest1의 맨 끝에 널문자는 유효하고, 널문자 자체를 복사하지 않으므로 dest1의 끝 부분까지 제대로 출력이 됩니다.

 

case2의 경우 sizeof(src) 를 이용해 널문자까지 dest2에 통째로 복사하는 예시입니다. dest2는 복사 이후 I love You \0(뒤에 dest2 부분) 와 같은 형태가 될 것인데 널 문자를 끝으로 인식하고 읽을 것이므로 컴퓨터는 I love You 까지만 읽고 끝냅니다.

 

case3의 경우 src에서 6글자만 복사해서 dest3에 붙여넣기 하고 있습니다. 널 문자까지 복사하지 않았으므로 정상적으로 잘 복사가 됩니다.

 

// memmove 복사

#include <stdio.h>
#include <string.h>

int main()
{
    char str[] = "abcdefg";

    memmove(str, str + 2, 4);
    printf("%s", str);
    return 0;
}

cdefefg

마지막으로 조금 특수한(?) 상황을 보겠습니다.

현재 붙여넣기할 곳은 str, 원본은 str + 2 번째가 됩니다.

첫번째 인자와 두번째 배열이 동일한 배열에 존재하는 상태로 어떻게 보면 복사할 위치와 붙여넣기할 위치가 같다고 볼 수 있겠네요.

 

물론 크게 문제는 없습니다. 컴퓨터는 시키는대로 하니깐요.. memmove() 함수는 큰 문제 없이 복사를 수행하게 되는데 

우선 str + 2 위치 즉 c라는 글자부터 4글자 복사하고 복사한 "cdef" 를 str위치 (str 배열의 시작주소 - 첫번째 요소 주소) 에 붙여넣기 하게 됩니다.

 

그래서 출력 결과는 cdefefg 가 되겠네요.

 

memcmp()

memcpm 는 'memory compare' 입니다.

사실 메모리 관련 함수에 strcmp도 있고 어셈블리어에 CMP 라는 명령어도 있듯이 compare를 cmp로 줄여쓰는게 사실 CS에선 많은 흐름으로 보입니다.

 

#include <string.h> //C++의 경우 #include <cstring>

int memcmp(const void* ptr1, const void* ptr2, size_t num);

함수 원형입니다. C언어는 <string.h> 를, C++는 <cstring> 을 include 해주시면 됩니다.

 

첫 번째 인자 ptr1은 비교할 첫 번째 주소입니다.

 

두 번째 인자 ptr2는 비교할 두 번째 주소입니다.

 

세 번째 인자 num은 몇 바이트 만큼 비교할 지 정하는 부분입니다.

 

반환값은 int형으로 

 

 

=> memcmp 함수는 ptr1이 가리키는 처음 num 바이트의 데이터와 ptr2이 가리키는 처음 num 바이트의 데이터를 비교하여 같다면 0, ptr1 > ptr2 라면 1, ptr1 < ptr2 라면 -1을 반환합니다.

 

결론적으로 memcmp() 함수를 호출하면 두 메모리 공간의 바이트가 같은지 , 큰지, 작은지 결과값을 알 수 있게 됩니다.

(함수 이름이 equal 이 아닌 compare인 이유입니다.)

 

사실 같은건 바이트가 전부 같다고 쳐도 크거나 작다는건 어떻게 비교하는지 잘 감이 안오실 수 있겠습니다만, 예제를 보면서 또 차근 차근 알아보겠습니다.

 

memcmp() 예제

// memcmp 함수

#include <string.h>
#include <stdio.h>

int main()
{
    char str1[] = "Hello";
    char str2[] = "Hello";
    char str3[] = "World";

    if (memcmp(str1, str2, 5) == 0)
        printf("str1과 str2는 같습니다.");
}

str1과 str2는 같습니다.

가장 간단히 사용해볼 수 있는 예시로 str1과 str2의 처음 글자 위치에서 5바이트 만큼 비교를 하고 있습니다.

사실 널문자까지 안읽어봐도 저희는 str1과 str2가 정확히 같다는걸 알 수 있죠. 3바이트씩 비교해도, 2바이트씩 비교해도 부분 부분 같은건 동일하기 때문에 0을 반환할 것입니다.

 

// memcmp 함수

#include <string.h>
#include <stdio.h>

int main()
{
    char str1[] = "Hello";
    char str2[] = "Hello";
    char str3[] = "World Hello";

    if (memcmp(str1, str2 + 6, 5) == 0)
        printf("same");
}

same

또한 꼭 시작위치에서 찾지 않아도 포인터 연산을 통해 특정 글자 위치에서 비교를 수행할 수 있습니다.

str2 + 6 의 경우 str2 위치에서 6글자 만큼 간 곳인데 World Hello 에서 H글자 부분이 되겠네요.

str1 시작점에서 5바이트는 Hello, str + 6 지점에서 5바이트는 Hello 이므로 역시 memcmp() 가 0을 반환하여 같음을 알 수 있습니다.

 

// memcmp 함수

#include <string.h>
#include <stdio.h>

int main()
{
    char str1[] = "HelloA";
    char str2[] = "HelloB";

    printf("%d", memcmp(str1, str2, 6));
}

-1

이제 memcmp() 가 0을 반환하는 case말고 이렇게 -1이나 1을 반환하는 case도 보겠습니다.

아까도 언급했듯이 메모리가 완전히 같지 않은 이상 memcmp(ptr1, ptr2, num) 에서 ptr1 > ptr 2 면 1을, ptr1 < ptr 2 면 -1을 반환한다고 했습니다.

 

memcmp() 의 비교 방식은 앞에서부터 바이트를 계속 읽다가 처음으로 다른 바이트가 나오면 그 두바이트를 비교하게 됩니다. 쉽게 이야기 해서 앞에서부터 읽으면서 계속 같으면 넘어가고, 이제 달라지는 부분이 나오면 그 부분부터 비교한다는 의미입니다.

 

지금 HelloA 라는 문자열과 HelloB 라는 문자열을 memcmp() 를 통해 6바이트 만큼 비교중인데, Hello 까진 같다가 Hello"A" 와 Hello"B" 부터 달라지는 부분이 생깁니다.

A와 B를 비교했을때 Ascii 코드 상 A라는 문자가 B보다 작으므로 A < B가 되어 ptr1 < ptr2의 상황으로 -1을 반환하게 됩니다.

 

사실 이렇게 ptr1 > ptr2 나 ptr1 < ptr2 를 따져서 계산하는건 정렬 이외에 생각나는 용도가 없습니다만 우선 메모리 같음을 비교할 수 있다는 점에선 꽤나 유용하게 사용할 수 있습니다.

 

 

#include <stdio.h>

int main()
{
    int arr1[100] = {
        0,
    };

    int arr2[100] = {
        0,
    };

    printf("%d", arr1 == arr2);
    return 0;
}

0

C언어에서 배열이 같은지 확인하려면 다음과 같이 같음연산자(==) 를 사용하는건 의미가 없습니다.

왜냐면 배열의 이름은 배열의 시작주소라서 시작 주소가 같은지 비교하는 코드가 되지, 배열 요소 자체가 똑같은지 확인하는 코드가 아니기 때문입니다.

 

#include <stdio.h>
#include <string.h>

int main()
{
    int arr1[100] = {
        0,
    };

    int arr2[100] = {
        0,
    };

    if (memcmp(arr1, arr2, sizeof(arr1)) == 0)
    {
        printf("두 배열이 같습니다.");
    }

    return 0;
}

for문으로 비교하는 함수를 제작하는 대신에, 이렇게 memcmp() 로 배열 내용이 같은지 비교가 가능합니다. 단 주의할점은 memcmp() 는 단순히 바이트 단위로 비교하는 함수기 때문에 두 배열 크기가 다르고 뒷부분 내용이 다르더라도 앞부분 까지만 다 같으면 같다고 나오므로,  배열 크기가 다른 경우엔 따로 처리가 필요하긴 합니다.

 

어디까지나 함수를 알려고 하는거라서 참고만 해주시면 되겠습니다.

 

memchr()

memchr는 'memory character' 입니다.

#include <string.h>  // C++ 에서는 <cstring>

const void* memchr(const void* ptr, int value, size_t num);
void* memchr(void* ptr, int value, size_t num);

함수 원형입니다. C언어는 <string.h> 를, C++는 <cstring> 을 include 해주시면 됩니다.

참고로 위 형태의 원형은 C++에서 정의된 방식으로, 오버로딩 되어서 나타내어 졌는데 C에선 오버로딩이 없으므로 아래와 같이 원형 하나만을 가지게 됩니다.

 

void * memchr(const void * ptr, int value, size_t num);

그래서 위 함수를 기준으로 memchr() 에 대해 알아보겠습니다.

 

첫 번째 인자 ptr은 검색을 수행할 부분의 시작 주소입니다.

 

두 번째 인자 value는 찾을 값입니다. int로 전달되지만 내부적으로 unsigned char 로 변환되어 사용됩니다. (그래서 문자를 넣어도 큰 문제가 없습니다.)

 

세 번째 인자 num은 검색을 시작한 부분 부터 몇 바이트 만큼 검색할지 정하는 부분입니다.

 

반환값은 void 포인터형으로 찾은 값의 주소를 반환합니다.

 

 

=> memchr() 함수는 메모리 블록에서 '문자'를 찾는 함수입니다. ptr이 가리키는 메모리의 처음 num 바이트 중에서 처음으로 value와 일치하는 값의 주소를 반환합니다. 

 

사실 위에서 다룬 memset() 이나 memcpy() 함수 역시 void 형 포인터로 어떤 주소를 반환하지만 함수를 사용하는덴 크게 중요하지 않아서 따로 다루진 않았습니다.

 

하지만 memchr() 의 경우 어떤 문자를 찾고 그 위치(주소) 를 반환하는 함수인 만큼 반환값이 중요합니다.

 

#include <stdio.h>
#include <string.h>

// str에서 z라는 문자가 처음 나타나는 위치를 찾는다
// memchr() 함수를 이용한다

int main()
{
    char str[] = "Hello, World!";
    char *p = (char *)memchr(str, '!', strlen(str));

    if (p == NULL)
        printf("!를 찾지 못했습니다.");
    else
        printf("!를 찾은 위치 : %d", p - str + 1);
}

!를 찾은 위치 : 13

다음은 Hello, World! 라는 문자열에서 ! 문자를 찾는 예제입니다.

기본적으로 memchr()이 찾은 위치의 주소를 void 포인터 형으로 반환하는데, 저희는 문자열 배열을 포인팅해서 탐색할 것이므로 명시적으로 (char *) 로 강제 형 변환 이후 char 포인터에 대입해줍니다.

 

이후 p 포인터 값이 NULL이면 못찾은거고 아니면 찾아서 p포인터에는 찾은 ! 문자의 주소 위치가 저장되어 있습니다.

이 주소를 p - str + 1 로 계산해서 %d로 출력해보면 찾은 글자의 position 을 알 수 있게 됩니다!

 

 

참고

https://eehoeskrap.tistory.com/264

https://blockdmask.tistory.com/441

https://blockdmask.tistory.com/444

https://blog.naver.com/sharonichoya/220508334439

https://modoocode.com/92

SNS 공유하기
💬 댓글 개
이모티콘창 닫기
울음
안녕
감사해요
당황
피폐

이모티콘을 클릭하면 댓글창에 입력됩니다.