memset?

#include <string.h>

    void *memset(void *s, int c, size_t n);

Linux manpage description

: The memset() function fills the first n bytes of the memory area pointed to by s with the constant byte c.

해석 및 부연설명

: void형 포인터 s가 가리키고있는 메모리 주소의 처음 위치부터 n바이트만큼에 저장되어있는 값을 상수 c로 채우고, 원래 포인터 s의 값을 리턴한다. 이 때 s가 가리키는 주소는 같지만 주소에 저장되어있는 값은 바뀐다.

ex)

char str[] = "abcdefg";
memset(str, '!', 5 * sizeof(char));
printf("%s\n", str);

코드 실행 결과

!!!!!efg

의문점 및 생각해볼점

  1. void형 포인터에 대해
  2. 변환하는 값이 int형?
  3. size_t는 뭘까
  4. char *strchar str[]의 차이?

void형 포인터

void *vp;

void형 포인터에 대한 개념을 참조한 링크
void형 포인터에는 모든 포인터 변수를 대입할 수 있다.
따라서 임의의 대상체에 대해 동작해야 하는 경우에 유용하게 사용될 수 있다.
반대로 임의의 포인터에 void형 포인터를 대입시에는 아래와 같이 형변환을 거쳐야한다. (C++에서는 반드시 그래야하지만 C에서는 괜찮다고 함)

ptr = (int *)vp;

*연산자 사용시에도 아래와 같이 형변환을 거쳐야한다.

int	i = 100;
vp = &i;
*(int *)vp = data;

위 코드처럼 int형 변수 i에 대한 주소는 void형 포인터 vp에 바로 대입이 가능하지만, *연산자로 값을 읽을 시에는 형변환이 없다면 어떤 데이터 형식인지 알 수 없기 때문.
같은 이유로 증감 연산자도 형변환 없이는 사용할 수 없다.
*과 형변환의 우선 순위는 같으므로 우측 우선 결합에 의해 형변환이 먼저 실행된다.

즉 void형 포인터는 단순히 메모리의 한 지점을 가리키는 기능만 가지는 포인터이다.


memset 함수의 두번째 매개변수

void *memset(void *s, int c, size_t n);

memset이 작동할 때 int형으로 받아온 c값은 실제로 unsigned char형으로 변환되어 사용된다고 한다.
manpage에 그런 말은 없었던 것 같아 좀 더 자세히 들여다보니, constant byte c라고 적혀있긴 했다.
+++ mac 환경에서의 메뉴얼에는 unsigned char형에 관한 언급이 있었다… 그럼 왜 애초부터 unsigned char형으로 받지 않고 int형으로? 에 대한 의문은 이미 존재했다.
동일한 질문이 담긴 링크
정확히 맞는지는 모르겠지만 memset이 함수 프로토타입이 생기기 이전부터 존재했고, 프로토타입이 없으면 char를 함수에 넣을 수 없었던 것 같다.
그래서 int형이 쓰였고 그 이후로도 굳이 int대신 char를 넣어서 얻을 이득이 없기에 그대로 쓰고있는 모양이다.

int array[100];
memset(array, 1, sizeof(array))

채워넣을 값이 내부적으로 unsigned char형으로 변환된다는걸 고려해야 하는 부분이 위와 같은 코드를 사용할 때이다.
기대한 결과는 00000000 00000000 00000000 00000001 으로 int형의 4바이트 메모리가 각각 채워지는 것이었겠지만, 실제로는 00000001 00000001 00000001 00000001 으로 채워져 전혀 다른 결과가 도출된다.


size_t

typedef unsigned int size_t     // 윈도우
typedef unsigned long size_t    // 리눅스

size_t는 ‘이론상 가장 큰 사이즈를 담을 수 있는 unsigned 데이터 타입’으로 정의된다고 한다.
컴파일러나 운영체제에 따라 그 크기가 달라져 각각의 경우에 size_t가 선언되어있는 타입이 다를 수 있다.


char * / char[] / const char*

참고한 링크
참고한 링크2

char *s1 = "abc";
char s2[] = "123";
const char *s3 = "!@#";
  • 우선 첫번째 char *s1 = "abc"의 형태로는 사용해서는 안된다. s1이 포인터 변수로 선언되었으나 “abc”는 리터럴(literal)이기 때문이다.
    리터럴이란?
    리터럴은 오직 읽기만 가능하므로 s1을 통해 문자열을 건드리는 순간 오류가 생긴다.
  • 반면 const char *s3 = "abc"의 경우에는 특별히 가능하다고 한다. 이 때 s3는 상수 포인터가 되어 상수 “abc”는 변경할 수 없으나, s3가 가리킬 주소값 자체는 변경이 가능하다.
  • char s2[] = "123"의 경우 char형 배열에 문자를 저장한 것이다. s2는 포인터 상수(일반적으로 배열명은 포인터상수라고 한다.)가 되어 주소값 변경이 불가능하지만, 주소에 저장되어있는 값은 얼마든지 변경할 수 있다.

const char *쪽을 좀 더 자세히 보면(const는 왼쪽에 있는 대상을 const화시키고 없을 경우 오른쪽으로 작용한다!)

  • const char * = char const * : 가리키는 주소값은 변경 가능, 대상의 값은 변경 불가능
  • char * const : 가리키는 주소값은 변경 불가능, 대상의 값은 변경 가능
  • char const * const = const char * const : 둘 다 변경 불가능

ft_memset 구현

void	*ft_memset(void *ptr, int value, size_t size)
{
	void	*result;

	result = ptr;
	while (size-- > 0)
		*(unsigned char *)ptr++ = value;
	return (result);
}

만약 value에 unsigned char 범위 이외의 값이 들어온다면 오버/언더플로우되어 강제로 unsigned char형으로 맞추어진다. 따라서 value는 형변환을 해줄 필요가 없는 듯 하다.