Eat Study Love

먹고 공부하고 사랑하라

SW 만학도/C

Review 3 - Pointer & Array in C

eatplaylove 2024. 7. 4. 15:17

https://eglife.tistory.com/70

 

Review 2 - Control Structures / Functions in C

https://eglife.tistory.com/69 Review 1 - C programming basics이번엔 C 언어에 대한 복습이다. Python, C, C++ 이 3대장 중에 제일 까다로운 녀석... 당연히 예전에 배웠던 것 죄다 까먹은 상태인데, 얼른 복습해봅

eglife.tistory.com

 
Function까지 다 배우고 C 실습을 해봤는데, 역시나 어렵다.
 
사실 C 문법을 모르는 건 없는데 그걸 응용해서 문제를 푸는 건 또 다른 영역이다.
 
뭔가 웃긴게, C를 이용해 정수의 인수분해를 코딩해봐라~ 라고 하면, 분명 답안 코드를 볼 때 이해가 가지 않는 것은 없는데, 그 인수분해라는 개념을 알아야 풀수 있는 문제들이다. 그러니까 결국, 이런 저런 문제를 다 풀어서 손에 익히는 것이 실습에선 정답이다. 마치 수학 내신공부를 할 때 다양한 유형의 문제를 다 배우는 것처럼 말이다.
 
더하기, 곱하기, 빼기만 배우고 수능문제를 풀려고 하면 몇 개나 풀리겠는가..
실전이 중요하다.
 
각설하고, 일단 빠르게 Pointer 개념 되짚기
 


1. Call by Value
 
Pointer가 필요한 이유를 설명해주는 예시,
 

#include "stdio.h"

void swap(int a, int b);

int main(void){
    int a = 7;
    int b = 5;
    printf("Before swap: a:%d , b:%d \n",a,b);
    
    swap(a,b);

    printf("After swap: a:%d , b:%d \n",a,b);
    return 0;
}

void swap(int a, int b){

    int temp;
    temp = a;
    a = b;
    b = temp;
    printf("In Swap a:%d, b:%d\n",a,b);
}
-------
Before swap: a:7 , b:5 
In Swap a:5, b:7
After swap: a:7 , b:5

 
Call by Value라 바뀌지 않는다.
 
Why? swap 함수 안에 있는 local Variable은 swap이라는 함수가 끝남과 동시에 사라진다.
 
좀 더 자세히 보기 위해서 memory 관점으로 봐보자.

 
main에 있는 5, 7과 swap에 있는 5,7의 주소값이 다르다는 것.
지금 main과 관련이 없는 XEFF7 / XEFF8 메모리에선 5와 7의 Swap이 잘 발생하지만, Swap함수가 끝남과 동시에 해당 주소들은 Pop! 사라진다.
 
즉, Value의 값을 복사해서 쓰는 함수(Call by Value)는 해당 함수 밖까지(Main 영역) 영향을 끼치지 못한다.
 


2. Pointer
 
기본적으로 Pointer라는 Variable은 특정 Object의 주소값을 저장한다.

Pointer의 Operator는 &* 가 있다.

Pointer 선언 시, 변수의 주소를 적어줘야 한다. 그냥 다이렉트로 int *ptr = &7 이런식으론 X
 
이걸.. Swap에 응용해봤는데, 뭔가 이상하긴 하지만 일단 swap은 된다.

#include "stdio.h"

void swap(int *a, int *b);

int main(void){
    int num1 = 5;
    int num2 = 7;
    int* a = &num1;
    int* b = &num2;

    printf("Before swap: a:%d , b:%d \n",*a,*b);
    
    swap(a,b);

    printf("After swap: a:%d , b:%d \n",*a,*b);
    return 0;
}

void swap(int* a, int* b){

    int temp;
    temp = *a;
    *a = *b;
    *b = temp;
    printf("In Swap a:%d, b:%d\n",*a,*b);
}
--------------
Before swap: a:5 , b:7 
In Swap a:7, b:5
After swap: a:7 , b:5

 
Pointer 와 Value 사이의 관계를 프린트문으로 좀 더 자세히 봐보자.
 

    int* intptr;
    int intvar = 5;
    intptr = &intvar;

    printf("intptr:%d\t&intvar:%d\t*intptr:%d\tintvar:%d\t&intptr:%d",intptr,&intvar,*intptr,intvar,&intptr);
    ------
    intptr:6422036 
    &intvar:6422036
    *intptr:5     
    intvar:5      
    &intptr:6422040

 
intptr은 intvar의 주소인 &intvar 값을 갖고 있고, intptr을 *(indirection operation) 하면 해당 주소 값인 intvar 값 5가 읽힌다. 그리고 intptr 자체의 주소인 &intptr도 따로 출력할 수 있다.
 
Swap call by reference의 경우 뭔가 찝찝하다 했더만, 그냥 함수 파라미터 포인터 값을 주소값으로 적어주면 되었다.

#include "stdio.h"

void swap(int* a, int* b);

int main(void){

    int a = 5;
    int b = 7;
    printf("Before swap: a:%d , b:%d \n",a,b);
    
    swap(&a,&b);

    printf("After swap: a:%d , b:%d \n",a,b);
    return 0;
}
void swap(int* a, int* b){

    int temp;
    temp = *a;
    *a = *b;
    *b = temp;
    printf("In Swap a:%d, b:%d\n",*a,*b);
}
-------------
Before swap: a:5 , b:7 
In Swap a:7, b:5
After swap: a:7 , b:5

 
Call by reference 를 메모리 관점에서 이해해보기

요렇게 Pointer를 쓰면 이 전과 달리, newSwap 함수 내의 동작이 Main에 있는 Valuable의 값을 바꾸는 기적을 창출한다.
 
Call by reference : 함수에 Parameter를 넘길 때, Variable의 값이 아닌 Variable의 주소값을 넘겨서 이 Variable의 값을 바꿀 수 있는 일종의 권한을 주는 것과 같다.

 
기본적으로 Pointer를 선언해 줄때는, NULL로 해야 이상한 값을 Pointer하는 것을 막을 수 있다.
 
그리고 int* ptr 이라는 선언문을 해석해보면, ptr이라는 variable은 indirection operator *를 적용했을 때 int type의 value로 바뀐다는 뜻이다. 마찬가지로, float* ptr은 *을 적용하면 float type의 value가 된다는 것.
 
우리가 scanf 를 할 때에도 굳이 &를 붙쳐서 &input이라고 했던 것도, input으로 들어오는 특정 값을 input value의 주소를 받아와 거기다가 넣는다는 뜻으로 결국 call by reference와 같은 개념이다.
 


 3. Array

 

Array는 Python의 List 구조 기저에 깔려 있는 가장 기초적인 데이터구조라고 보면 된다.

 

한 Array에 여러 Data type이 들어갈 수 없어서 Data type이 통일되게 initialize를 해줘야하며, array의 크기를 마음대로 조정할 수 없어서 처음에 array의 크기까지 선언해줘야 한다.

    int mid[NUM_STUDENTS];
    int final[NUM_STUDENTS];
    int total[NUM_STUDENTS];
    
    //input exam scores

    for(int i=0; i<NUM_STUDENTS;i++){
        printf("Input mid score for student %d:",i);
        scanf("%d",&mid[i]);
        printf("Input fin score for student %d:",i);
        scanf("%d",&final[i]);
    }

    // Calculate total scores
    for(int i=0;i<NUM_STUDENTS;++i){
        total[i] = mid[i] + final[i];
    }
    // Output the total scores
    for(int i=0;i<NUM_STUDENTS;i++){
        printf("Total성적 of students %d is %d \n",i,total[i]);
    }

 

이런식으로 array구조를 이용한다. int arr[10]은 int 값이 들어가는 array 10칸을 arr이라는 Variable 이름으로 선언한다는 것이다.

 

그리고 참고로, C에선 OOP가 되지 않는다. Class를 만들어서 OOP를 하는 건 Python과 C++만!

 

특이하게, Character의 경우 원래 작은 따옴포 ' ' 로 표현을 하는데, C에선 Char의 Array형태를 String이라고 한다.

Print할 때는, Char은 %c, String은 %s 를 써준다.

 

String의 경우 char word[10] = "EXAMPLE" 이런 식으로 선언할 수 있는데, E,X,A.. 각 Character가 Array 칸을 차지하고 나서 마지막에 '\0' 라는 특수문자가 String의 끝을 알린다. 즉, String의 Char 개수보다 '\0'를 고려해 최소 1칸은 더 큰 Array size를 초반에 선언해줘야 한다.

 

    char words[6] = "winter";
    printf("%s\n",words);
    char words2[8] = "winter2";
    printf("%s\n",words2);
    ---
winter??
winter2

 

이렇게 words[6]은 winter의 Character 개수에 딱 맞게 6개로 array size를 정했는데, 그러다 보니 End Mark '\0'이 들어갈 자리가 없어서 print가 제대로 되지 않았다.

 

참고로 \0의 아스키코드 대응값은 0이다.

 

Pyton에선 문자 하나던 문장이던 ' ' , " " 구분 없이 썼지만, C/C++에선 Single Character은 ' '로 표현, String 문장은 " " 로 따옴표를 구분해서 표현한다.

 

4. Array with Pointer

 

* : Dereferencing 이다. Linked list와 달리 Array는 따닥 따닥 붙어 있어서, n 번째 array 칸에 저장되어 있는 value를 읽어오기가 편하다. 그냥 Simple하게 array이름을 values라고 했을 때, values+n 이 그 array의 n번째 칸 주소이고, 값을 불러오려면 *(values + n)을 해주면 된다.

 

위 예시에서, values의 경우 "int" type의 array로 선언이 되어버린 상태라 다른 type array로 변경이 불가하지만 valPtr의 경우 다른 array를 pointing 할 수 있다.

 

 

Bolt 표시 되어 있는 inputValues[]의 경우 대괄호를 통해 array의 주소값을 함수에 넘기는 것이라 call by Reference 개념으로 보면 된다.

 

#include <stdio.h>
// #define NUM_STUDENTS 5
#define MAX_NUMS 3

int Average(int inputValues[]);

int main(void){
    int mean;
    int num[MAX_NUMS];

    printf("Enter %d nums,\n",MAX_NUMS);
    for(int i=0;i<MAX_NUMS;i++){
        printf("INPUT NUM %d: ",i);
        scanf("%d",&num[i]);
    }

    mean = Average(num);
    printf("The average of these nums is %d\n",mean);

    return 0;
}

int Average(int num[]){
    int sum = 0;
    for(int i=0;i<MAX_NUMS;i++){
        sum += num[i];
    }
    int ans = sum/MAX_NUMS;
    return ans;
}

 

다시 한 번 말하지만, 요지는 Functions 의 input type 중 int SOMETHING[ ] -> 이건 array를 받는 거고, Call by Reference라는 것!

 

Array의 이름 SOMETHING 이라는 것은 그 Array의 첫 번째(index 0)의 주소를 갖고 있다.(== &SOMETHING[0])

 

그렇다면 2D Array에선 Arrary의 Memory 주소가 어떻게 될까?

 

2D에서 image[640][480] 이라는 Array가 있을 때, image[0],image[1].. 은 위 그림과 같이 크기가 480인 Array의 첫 번 째 부분 주소를 나타내고, image라는 이름 자체도 +1,+2 될 때마다 위 그림과 같이 Group을 건너뛴다.

 

int arr[2][3];
for(int i=0;i<2;i++){
    for(int j=0;j<3;j++){
        arr[i][j] = i+j;
    }
}

for(int i=0;i<2;i++){
    for(int j=0;j<3;j++){
        printf("%d ",arr[i][j]);
    }
    printf("\n");
}
printf("%d %d %d %d\n",arr,arr[0],arr+1,arr[1]);
printf("%d %d %d %d\n",arr,&arr[0][1],&arr[0][1]+1,arr[1]);
---------
0 1 2 
1 2 3
6422000 6422000 6422012 6422012
6422000 6422004 6422008 6422012

 

2차원 array를 만들어봤는데, 대충 arr = arr[0] 이고 arr+1 = arr[1] 이다.

그리고 &arr[0][1]+1 은 &arr[0][2]와 같다. 지금 int array니까 1칸당 4byte로 생각하면 계산이 된다.

 

2D image를 한 번 Dereference를 하면 그 때 부터 +1,+2,,,+n 은 크게 크게 건너 뛰는게 아니고 image[0] .. image[n] 칸 안에서 주소값이 더하기가 된다. image 이름 자체로 value값을 읽어 오려면 2D라서 dereference를 2번 해줘야 한다.

반면, image[1] , image[n] 등 대괄호가 이미 하나 있는 녀석들은 dereferencing *을 1번만 해줘도 일단 value값이 읽힌다.

 

좀 복잡해 보이는데, 정리하면 아래와 같다. 숙지가 필요하다.

 

그리고 Array를 다룰 때 Array size가 오바되는 건 없는지 항상 체크해줘야 한다. C에서는 Compile 과정에서 해당 error를 잡아주지 않고 넘어가는 경우가 다반사이기 때문!

 

멀고도 험한 C Array의 세계..

 

- E. O. D -

 

'SW 만학도 > C' 카테고리의 다른 글

Review 5 - Dynamic(Structure) in C  (0) 2024.07.08
Review 4 - File I / O in C  (0) 2024.07.07
Review 2 - Control Structures / Functions in C  (0) 2024.07.03
Review 1 - C programming basics  (0) 2024.07.03
5. Pointer - Motivation in C  (1) 2024.03.26