void do_something(int* num1, int* num2)
{
if (num1 == num2) {/*주소 비교*/
/* 코드 생략 */
}
}
void do_something(int* num1, int* num2)
{
if (*num1 == *num2) {/*값 비교*/
/* 코드 생략 */
}
}
void print_pointer_size()
{
char ch = 'c';
int number = 934563;
float pi = 3.1415f
char* char_ptr = &ch;
int* int_ptr = &number;
float* float_ptr = π
printf("char size : %d, char* size: %d \n", sizeof(*char_ptr), sizeof(char_ptr));
printf("int size : %d, int* size: %d \n", sizeof(*int_ptr), sizeof(int_ptr));
printf("char size : %d, float* size: %d \n", sizeof(*float_ptr), sizeof(float_ptr));
}
void print_scores(int score[])
{
size_t size = sizeof(scores); /* 4 반환 */
/* 코드 생략 */
}
매개변수로 받은 배열
void print_scores(int scores[])
{
size_t size = sizeof(scores);
/*코드 생략*/
}
print_scores(my_scores);
포인터
void print_money(float* dollar)
{
size_t size = sizeof(dollar);
/* 코드 생략 */
}
print_money(&my_money);
둘이 매우 유사함, 배열을 포인터에 배열할 수 있을까?
int nums[6] = { 0, 1, 2,3, 4, 5 };
int* ptr = NULL;
ptr = nums; /* 컴파일 됨 */
ptr = nums[0]; /* 컴파일 오류 */
incompatible integer to pointer conversion assigning to 'int *' from 'int'
- nums[0]은
int지int*가 아님.
int nums[6] = { 0, 1, 2,3, 4, 5 };
int* ptr = NULL;
ptr = nums; /* 컴파일 됨 */
ptr = &nums[0]; /* 컴파일 됨 */
포인터에 정수 1을 더한다?
- 포인터의 위치를 다음 데이터의 위치로 이동, 1 바이트를 더하는 게 아님.
뺄셈도 마찬가지.
++나 --도 마찬가지
int* ptr1 = nums + 3;
int* ptr2 = &nums[3];
int nums[3] = { 10, 20, 30 };
int* ptr = nums;
printf('%d, %d, %d \n", nums[1], ptr[1], *(ptr + 1));
20, 20, 20
int* ptr = nums;
printf("%d, %d, %d \n", nums[1], ptr[1], *(ptr+1));
nums[1] = ptr[1] = *(ptr +1)
이 모두 컴파일러에게 똑같은 의미
지금 이 연속되는 메모리에서 시작에서 한 칸 건너 뛰어서 두 번째를 보여줘
그게 어디에 있다고?
지금 데이터는 int, int는 4바이트
그러면 4바이트 한 번 건너뛰면 되지
포인터 산술 연산에도 배열 첨자 연산자에도 동일하게 적용됨
배열의 모든 요소 더하기 예1
int sum(int* data, const size_t length)
{
int result = 0;
size_t i;
for (i = 0; i < length; ++i) {
result += data[i];
//result += *(data + 1);
}
return result;
}
/*메인 함수 */
int nums[6] = { 0, 1, 2, 3, 4, 5 };
int result = sum(nums, 6); /* 15 */
int -> char 캐스팅은 무엇을 캐스팅 하는 걸까?
그 메모리 주소에 어떤 형이 들어있는지 알려주는 것?
바꾸고 나면 실제 이 속에 있는 데이터 내용은 char* 일까? No.
int int_array[] = { 27, 65};
int* int_ ptr = int_array;
int_ptr (char*)int_ptr + 1;
한 바이트만큼 이동 후 거기서 4바이트를 읽음.
뺄셈을 제외한 사칙 연산은 모두 지원 안함
두 주소를 더한다고 무슨 의미가?
곱,나눗셈은 더더욱…
뺄셈의 경우 두 주소 사이에 들어갈 수 있는 데이터 수를 반환
따라서 포인터가 아니라 정수를 반환
`int sub = &nums[5] - &nums[1]; / 4 /
int i ;
int num = 1024;
int nums[3] = { 34, 135, 49 };
int* ptr = nums;
for (i = -1; i <= 3; ++i) {
printf("%p: %d\n", (void*)(ptr + i), *(ptr + i));
}
그렇다고 안 쓰기엔 너무나 강력함
값을 복사하는 것보다 주소에 접근하는 게 훨~!씬 빠름.
무작정 안 스는 것보단 잘 쓰는 게 좋음
훌륭한 프로그래머가 되기 위함.
포인터 정도는 씀.
C/C++ 프로그래머가 엄청난 존경을 받는 이유도 이걸 할 수 있어서.
sizeof(배열) 과 sizeof(포인터)는 다른 값을 반환
sizeof(배열) : 배열의 총 크기를 반환
sizeof(포인터): 포인터의 크기를 반환
int nums[3] = { 34, 135, 49 };
int* ptr = nums;
size_t size1 = sizeof(nums);
size_t size2 = sizeof(ptr);
C는 C#이나 Java 처럼 문자열(string) 자료형이 없음
그럼 어떻게 문자열을 표현?
char 배열을 이용해서 문자열을 표현
가령 "Friday" 라는 단어를 저장한다면 총 6rodml 요소를 가진 char배열을 만듦
문자열이 끝나는 지점을 알려주기 위해 널 문자(null character)라고 하는 특별한 문자를 항상 맨 마지막에 넣어줌
널 문자 : 값은 0으로 \0 <-백 슬래시와 0을 합쳐서 표현함.
이 문자열을 초기화 하는 방법은 두가지
방법 1
char day1[] = "Monday";
배열에 차례대로 문자가 들어간 후 마지막에 \0 가 들어감
함수 안에서 사용하면 스택 메모리에 저장됨
함수 호출할때 배열을 위한 스택메모리를 잡아주는데 다른 메모리(데이터 섹션)에서 가져와 스택에 담아줌.만약 함수 밖, 파일에 있어도 데이터 섹션에서 가져옴.
방법 2
`char* day2 = "Monday";
포인터 변수는 스택에 저장
실제 문자열은 데이터 섹션에 저장
스택에 저장된 문자열은 수정해도 괜찮지만 데이터 섹션에 저장된 문자열은 수정할 경우 '결과가 정의되지 않음'
후자의 경우 문자열이 읽기 전용
연산자 결합 법칙은 사실 별로 고민할 이유가 없음
익숙한 것들은 그냥 쓰고 아닌 것들은 괄호 치는게 일반적
그래서 사람들이 별 신경 안씀
연산자 결합 법칙이란?
동일한 우선순위를 가지는 연산자들이 있으면 어떤 방향으로 연산자를 적용하냐 의미 (왼 쪽에서 오른쪽 , 혹은 오른쪽에서 왼쪽)
대부분이 왼쪽에서 오른쪽 그래서 신경 안씀
왜 C에서는 이걸 묻냐? 다른 언에서는 안봐서, 익숙하지 않은 연산자 *, &가 나와서…
int num = *p++;
int num = *++p;
int num = ++*p;
int num = (*p)++;
int sum(int* start, int* end)
{
int result = 0;
int* p = start
while (p < end) {
result += *p++;
}
return result;
}
/* 메인 함수 */
int nums[] = { 10, 20, 30, 40, 50 };
int result = sum(nums, nums + 5);
*p++ 이 더 빠른가?
int sum(int* start, int* end)
{
int result = 0;
int* p = start;
while (p < end) {
result += *p++;
}
return result;
}
*p++ 을 더 많이 씀
int display_user(int* id, char* name)
{
int result;
/* id를 읽기 전용으로 막 사용 */
/* name를 읽기 전요응로 막 사용 */
/* 코드 100줄 */
*id = 0;
/* 코드 100줄 */
return result;
}
기본 자료형 변수의 경우 const 를 붙이면 그 변수에 저장한 값을 변경할 수 없었음
보통 이게 반드시 필요하다고 느끼진 않음.
따라서 이걸 반드시 붙이라고 강요 안하는 코딩 표준도 많음
실수가 발생해도 큰 문제가 발생하지 않기 때문
함수 범위 내에서 발생할 수 있는 실수를 막는 정도
그럼 포인터 변수에 const를 붙이면 뭑 ㅏ바뀌지 말아야 할까?
포인터 변수에 저장되어 있는 것은 무엇
메모리 주소
그래서 const 포인터는 메모리 주소를 바꿀 수 없음
포인터 변수는 오른쪽에서 왼쪽으로 읽음 따라서.
int* const p = #
p is a const pointer to int
뭐가 const? 그 주소 자체, 즉, 포인터
왜 오른쪽에서 왼쪽으로 읽냐? 두 번째 const 때문.
const int* p = &num1; /* 방법 1 */
int const * p = &num1; /* 방법 2 */
실수가 있을 경우 함수 내에서 뿐만 아니라 전역적으로 문제가 발생
이게 바로 전의 경우(주소 보호) 보다 더 중요
이 const는 반드시 신경써야 함
그 주소에 저장되어 있는 값을 변경하는 것을 방지
오른쪽에서 왼쪽
방법 1 : "p is a pointer to int, which is const"
방법 2 : "p is a pointer to const int"
논리적으로 방법 2가 더 말이 되나 흔히 방법 1로 씀
포인터 아닌 int를 const로 만들때도 const int 라고 함으로 비슷하게 보이게하려고 방법 1을 많이 씀
int const p = &num1; / 그 메모리 주소에 저장되어 있는 값을 변경하는 걸 금지 */
const int p = &num1; / 메모리 주소를 변경하는 것을 금지 */
// 주소를 보호하는 const 포인터
char ch = 'A';
char* const p = &ch;
*p = 'Z';
// 주소가 가리키는 값을 보호하는 const 포인터
char ch1 = 'A';
char ch2 = 'A';
const char* p = &ch1;
p = &ch2;
const int* const p = #
오른쪽에서 왼쪽 읽기
"p is a const pointer to const int"
초기화 된 후 절대 바뀌지 않는 변수가 있을 때 정도만 유용.
예 : 전역변수, 구조체 멤버 변수
주소에 저장되어 있는 값을 보호하는const가 더 중요함.
const int* p = # /* 주소에 저장된 값을 바꿀 수 없음 */
int const* p = # /* 주소에 저장된 값을 바꿀 수 없음 */
int* const p = # /*p가 가리키는 주소를 바꿀 수 없음 */
const int* const p = # /* 둘 다 바꿀 수 없음 */
* "포인터, 무엇을 가리키냐면"근데 제거하지 말아야함.
기본 자료형에서 큰 문제가 아니나
어차피 매개 변수가 값을 복사해옴
그 매개 변수의 값을 바꾼다고 원본이 바뀌지 않음
그러나 const를 가리키는 포인터의 경우 문제가 됨
const를 제거하고 값을 바꾸면 원본이 바뀜
const는 최대한 다 붙이는게 좋다
반드시 const가 필요없는 경우가 아니라면!
const 캐스팅은 하지 말 것
함수 시그니처에서 안 바꾼다고 약속하고 어기지 말자.
함수의 범위에 상관 없이 한동안 사용하고자 하는 데이터가 있는데 다음과 같은 경우에 해당하면 사용
그 데이터의 크기를 컴파일 도중에 알 수 없거나
프로그램 실행수명 보다는 짧은 시간 동안만 사용하려고 할 때
동적으로 할당된 메모리는 역시 연속된 메모리 덩어리
따라서 포인터가 적합 (배열과 비슷한 이유)
데이터 구조를 구현할 때
연결 리스트, 트리 등과 같은 데이터 구조에 포인터가 적합
임베디드 프로그래밍 등에서 하드웨어에 있는 메모리에 직접 접근해야 할 때
예 : 어떤 하드웨어는 화면을 보여주려면 특정 메모리 위치에 이미지 데이터를 직접 복사해줘야 함.
포인터도 그냥 변수니 당연히 포인터를 저장하는 배열도 있음
그러면 어떻게 배열을 선언해야할까?
int* num_pointers[3];
C# : string[][] classrooms = new string[3][]; 과 비슷한 개념.
int num1[3] = { 11, 22, 33};
int num2[1] = { 90 };
int num3[4] = { 88, 36, 37};
int* num_pointers[3];
num_pointers[0] = nums1;
num_pointers[1] = nums2;
num_pointers[2] = nums3;
size_t 배열이 필요
void print_array(int* const data[], const size_t size, const size_t lengths[])
{
size_t i;
size_t j;
const int* p;
for (i = 0; i < size; ++i){
p = data[i];
printf("nums[%d]:" , i);
for (j =0; j < lengths[i]; ++j) {
printf(" %d", p[j]);
}
printf("\n");
}
}
void do_magic(int matrix[][10], size_t m)
{
}