calloc?

#include <stdlib.h>

    void *calloc(size_t nmemb, size_t size);

Linux manpage description

: The calloc() function allocates memory for an array of nmemb elements of size bytes each and returns a pointer to the allocated memory. The memory is set to zero.
If nmemb or size is 0, then calloc() returns either NULL, or a unique pointer value that can later be successfully passed to free().
If the multiplication of nmemb and size would result in integer overflow, then calloc() returns an error.

해석 및 부연설명

: 크기가 size인 메모리를 nmemb만큼 할당하고 모두 0으로 채워넣은 후 그 시작주소를 반환한다. 즉, 할당 자체는 malloc(nmemb * size)와 동일하다.

ex)

char	*str;
int		i;

i = 0;
str = (char *)calloc(10, sizeof(char));
while (i < 10)
{
	str[i] += '0';
	i++;
}
printf("%s\n", str);

코드 실행 결과

0000000000

의문점 및 생각해볼점

  1. calloc(0, 0)?
  2. 할당된 범위를 넘어간다면?
  3. malloc과의 차이점
  4. 형변환은 필요한가?

0 할당시

char	*str1;
char	*str2;
str1 = calloc(0, 0);
str2 = NULL;
printf("%p\n%p\n", str1, str2);

이 코드를 실행해보면 아래와 같은 결과가 나온다.

0x563b367cc2a0
(nil)

calloc(0,0)이나 malloc(0)의 반환값은 메뉴얼에 나와있던 것처럼 NULL 또는 역참조할 수 없는 non-NULL pointer인데, 이 코드의 실행 결과에서는 후자가 나온 모양이다.
그런데 이게 undefined behavior라, 따로 ft_calloc 함수에서 처리할 필요는 없고 그냥 이런식으로 작동하니 최대한 피해야한다는걸 알고있는 정도면 되는 듯 하다.


할당되지 않은 메모리를 건드렸을 때

char	*str;
int		i;

i = 0;
str = (char *)calloc(1, sizeof(char));
while (i < 10)
{
	str[i] = i + '0';
	i++;
}
printf("%s\n", str);

이렇게 코드를 짜고 실행해보면 결과가 이렇게 나온다.

0123456789

분명 할당한건 1 뿐인데, 어째서 에러 없이 10까지 전부 채워진걸까.
당연히 이런 궁금증을 나만 가지고 있던건 아니었다.
해당 질문이 올라와있는 링크
결론은 C언어의 구조상 그런 것이고, 이렇게 쓰면 언젠가 어디선가 문제가 생길테니 주의해야 한다는 소리인 것 같다.


calloc vs malloc

malloc에서 곱해진 값을 인자 두개로 나눈 것이 calloc인데(초기화 부분을 제외하면), 왜 굳이?
그에 대한 해답은 오버플로우 처리에서 찾을 수 있었다.
해당 질문에 대한 스택오버플로우 링크
mallocsize_t형의 범위를 넘어간 오버플로우를 검사하지 못하지만, calloc처럼 인자를 나누면 가능한 모양이다. 이부분은 좀 더 공부가 필요할 것 같다.


동적할당시 형변환에 관해

활발한 토론장
댓글들을 보면 형변환이 필요하다고 주장하는 쪽도 있고, 아닌 쪽도 있다.
어느 쪽이 더 적합한지는 아직 식견이 좁아 잘 판단하지는 못하겠고… 그냥 각각의 경우에 어떤 주장을 펼치는지만 대충 요약해보자면

  1. 형변환은 필요없다
    • void *형은 어짜피 자동으로 캐스팅된다.
    • 반복을 유발하고 읽기 힘들게 만든다.
  2. 형변환은 필요하다
    • 에러를 명확하게 확인할 수 있다.
    • 기계가 체크할 수 있도록 반복하는 것은 때때로 좋을 수 있다.
    • C와 C++ 사이의 이식을 쉽게 해준다.

일단은 그냥 쓰던대로 써야겠다.


ft_calloc 구현

void	*ft_calloc(size_t nmemb, size_t size)
{
	void	*temp;

	temp = malloc(nmemb * size);
	if (!temp)
		return (NULL);
	ft_memset(temp, 0, nmemb * size);
	return (temp);
}

할당은 malloc, 초기화는 이미 만들어둔 ft_memset함수를 이용해 간단하게 구현했다.