unit 70,72 질문이요!
, 김 상섭님이 작성①
unit 70
p.830 에제에서
char *s1 = "Hello, world!";
...
fwrite(s1, strlen(s1), 1, fp);
라고 되어 있는데
fwrite는 값의 메모리 주소를 넣어야 하므로
fwrite(&s1, strlen(s1), 1, fp); 으로 해야 하지 않나요??
unit 72
p.859 에제에도
struct Data d1;
...
fwrite(&d1, sizeof(d1), 1, fp);
이렇게 &d1으로 되어있더라구요..!
②
unit 70
p.830 예제에서
char *s1 = "Hello, world!";
...
fwrite(s1, strlen(s1), 1, fp);
unit 72
p.859 예제에는
struct Data d1;
...
fwrite(&d1, sizeof(d1), 1, fp);
같은 fwrite 함수인데
어디는 strlen을 사용하고 어디는 sizeof를 사용하는지 궁금합니다..!
감사합니다.
Re: unit 70,72 질문이요!
, 도장_ 관리자님이 작성포인터쪽 이해가 아직 안 된 것 같습니다.
복습하면서 원리를 이해해보세요.
https://en.cppreference.com/w/c/io/fwrite
buffer가 void*로 되어 있는 건 범용 포인터라는 뜻입니다. 역시 범용 포인터 부분 복습하시면 됩니다.
즉, buffer에는 int, long, 배열, 구조체 등 어떤 자료형이든 데이터를 쓰기 시작할 메모리 주소만 전달하면 된다는 뜻입니다.
void* 범용 포인터로 선언하지 않는다면
fwrite_int(const int *buffer, ...) 이런식이 될텐데, 이건 불가능하죠.
만약 사용자가 x, y 좌표를 담은 struct point 구조체를 만들고, 메모리를 할당하고, 여기에 쓰기를 원한다면 fwrite_struct_point(const struct point *buffer, ...) 이런 함수를 만들어야 하는데, 내용은 모두 같고, 받은 인자만 다른 함수가 무한개로 생길 겁니다.
또한, 표준 함수 제작자가 앞으로 사람들이 어떤 구조체나 자료구조를 선언하고 쓸지 어떻게 알아서 만들 수 있을까요? 그래서 범용 포인터를 쓰는 겁니다.
타입에 상관없이 메모리 주소만 알려달라는 뜻입니다.
buffer의 타입을 보면 void*이므로 pointer to void입니다.
void는 '비어 있다'는 뜻이 아니라 '아무거나 다 된다'는 뜻이니까 pointer to x 정도로 이해하면 됩니다.
char *s1이면 s1의 타입은 pointer to char입니다.
이미 주솟값입니다. '주솟값'이라는 표현도 헷갈리게 하는 것 같으니 이런 설명 하지 말고, 그냥 영어로만 봅시다.
fwrite의 buffer는 pointer to x이고,
s1도 pointer to char입니다.
"pointer to"로 일치하니까 문제 없음.
"pointer to"로 일치하니까 문제 없음.
따라서 fwrite(s1, ...)
이렇게 써야 합니다.
struct Data d1에서 d1의 타입은? struct Data입니다.
&d1은? address of struct Data입니다.
fwrite의 buffer는 pointer to x입니다.
address of == pointer to는 호환되니까
&d1으로 써야 합니다.
주소로 처리할 곳에 d1으로 전달하면 값을 전달하니까 타입 미스매치.
buffer의 타입은? void*이니 pointer to void == pointer to x라고 생각하기.
d1은 struct Data이니까
d1과 buffer는 타입 미스매치. 잘못된 코딩.
&d1은 address of struct Data이니까 buffer의 pointer to와 호환.
반복하세요.
영어로 쓰세요. 영어로 생각하시고. 영어로도 정식 표기임. 유학 가시든 해외에서 발표하시든 다 저렇게 읽고 씁니다. 미리 배운다 생각하세요.
역순 번역 한국어로 생각하면 다 꼬입니다. 포인터 어렵게 배우는 애들 특징.
함수를 사용할 때는 타입 매치냐 타입 미스매치냐만 생각하면 됩니다.
pointer to와 address of는 호환된다.
pointer to와 array of는 호환된다.
왜? 다 주솟값이니까.
해당 주소에 접근해서 값을 쓴다 --->> pointer to, address of, array of로 시작하는 타입들임.
해당 변수에 접근해서 값을 쓴다 ---> int, long, double나 struct 같은 타입들
해당 변수에 접근해서 값을 쓴다 ---> int, long, double나 struct 같은 타입들
해당 주소에 접근해서 int 값을 쓴다 --->> pointer to int
해당 주소에 접근해서 int[10], int 값 10개 쓴다 --->> pointer to int로 접근하고, 해당 메모리 주소는 int 10개 쓸 공간 malloc으로 할당해야 함.
또는 array of int로 미리 int arr[10]으로 선언된 배열이어야 함.
또는 array of int로 미리 int arr[10]으로 선언된 배열이어야 함.
fwrite의 두 번째 인자는 해당 메모리에 몇 바이트나 쓸지 묻는 크기, size입니다.
문자열의 길이는 strlen() 함수로 알아내는 것
구조체의 크기는 sizeof() 연산자로 알아내는 것. sizeof()는 ()를 쓰지만 함수 아님. 연산자임.
C 언어 표준에 연산자로 되어 있음.
sizeof를 함수로 소개하는 책, 강의가 있으면 패스하시길. 표준 모르고 가르치는 중입니다.
다만, C 언어에서 문자열은 NULL 문자를 만날 때까지의 문자 개수를 세는 방식입니다. 문자열의 길이만 계산하고, 배열의 길이는 계산하지 않습니다.
sizeof() 연산자는 변수, 자료형의 크기를 계산하는 연산자입니다. 차이는 다음 코드로 확인하세요.
char arr[10] = "Hello";
int size = sizeof(arr); // 전체 배열의 크기 계산
printf("%d %d\n", strlen(arr), size); // 5 10
malloc으로 1000바이트를 할당했지만, "Hello"라는 5글자만 실제로 입력으로 받았다면 fwrite로는 5글자만 저장하는 게 타당합니다.
그러니 문자열을 저장할 때는 문자열의 길이만 세서 strlen(arr)처럼 쓰는 게 맞습니다.
게임 저장을 구현할 때 게임 저장 슬롯이 10개인데 현재 1개만 쓰고 있다면 쓰고 있는 1개만 저장하는 게 맞습니다. 쓰지도 않는 빈 슬롯 9개까지 모두 다 저장한다면 게임 개발 엉터리로 했네라고 생각할 겁니다. 슬롯 1개 저장에 10초가 걸린다면 10개의 슬롯이라면 항상 100초가 걸린다는 뜻이니까요.
strlen은 문자열의 길이를 세는 함수이고, 문자열 끝의 NULL이 생략됩니다. "Hello"이면 5를 반환하지만, Hello를 저장하려면 C 언어는 끝에 NULL을 붙여 6바이트가 필요합니다. 이건 <C 언어 코딩 도장>의 수많은 예제에서 반복되는 코드입니다.
배열의 전체 크기는 sizeof로 계산합니다.
문자열만 예외적으로 strlen으로 계산한다고 생각하면 됩니다.
이 글도 관리자(저자 아님)가 작성했습니다.
Re: unit 70,72 질문이요!
, 김 상섭님이 작성충격적인 설명 감사합니다..
원리를 이해한다고 노력하는데도 다음 진도를 나가면 알듯말듯한 느낌이 들어 지금까지 설명해주신 부분들을 복기하는데도 쉽지가 않네요...
그럼에도 항상 훌륭한 설명 감사합니다..!