개발 기록/행렬 계산기

행렬 계산기 개발하며 배운 것 - Call by value, Call by Reference, 등가포인터, 다차원배열 등...

LiDARian 2021. 5. 30. 15:55
반응형
  1. 구조체 선언 시에는 초기화가 불가능하다.

visual studio에서는 가능했었던 것으로 기억하는데, GCC를 이용해서 컴파일 할 때에는 이게 안된다.

참고할 만한 링크
https://www.linuxquestions.org/questions/programming-9/gcc-error-%91foo%92-has-no-member-named-%91bar%92-877951/

  1. 함수(기능)별로 구분하고 그에 해당하는 기능들을 하나씩 추가해서 테스트 우선해보는 방식으로 작성하는 것이 좋다.
  1. Call by value, Call by reference를 구분해야만한다.

C언어는 공식적으로 Call by value만을 지원한다. 그리고 Call by reference를 pointer를 통해서 흉내낸다.
다른 언어보다 C언어에서 이 문제가 더 중요하고 더 헷갈리는 것 같다. 이 문제 때문에 몇 일을 버렸는지...

참고할 만한 링크
https://m.blog.naver.com/PostView.naver?blogId=whdgml1996&logNo=221038044040&proxyReferer=https:%2F%2Fwww.google.com%2F

  1. 다차원배열과 포인터 표현

이게 컴파일 에러에 영향을 끼친 것은 아니어서, 그저 추가적인 공부가 되었다. 메모리의 구조가 다차원배열과 다중 포인터가 다르다는 것을 알 수 있었다.

배열과 포인터의 차이

배열은 길이정보와 그 주소를 포함한다. 포인터 변수는 단지 포인터로서의 정보(주소)만 담고 있다. 따라서 배열을 포인터형으로 변환하면, 길이정보를 잃는 것이다.

다차원배열과 등가 포인터

2차원 배열의 경우 arr[5][5] = (*ptr)[5]이 같다. 이는 5개의 인덱스를 가지는 배열을 통해 인덱스를 채우는, 5개의 인덱스를 가진 배열 혹은 포인터이다. *ptr[5]주소를 담는, 5개의 인덱스를 가진 배열을 의미한다.

2차원 배열의 경우 char arr[5][5]에 대해서 arr + 1로 인덱싱한다고 하면, arr의 주소에서 1만 추가되는 것이 아니라, arr주소에서 그 다음의 인덱스인 arr+ 5의 주소로 인덱싱된다.

참고할만한 링크(다 읽어보는 걸 추천)
http://pelex529.blogspot.com/2009/01/2.html
https://mynameisdabin.tistory.com/32
https://mynameisdabin.tistory.com/9?category=786516
https://pridiot.tistory.com/217
https://kookyungmin.github.io/language/2017/08/11/C04/
https://skmagic.tistory.com/67

  1. 그 외의 포인터 문제들

포인터와 문자열 문제

name은 포인터일 뿐 문자열을 저장할 수 있는 곳이 아니다. 즉 아래와 같은 방법을 사용하려면 포인터가 아닌 배열로 선언해야한다. 혹은 동적할당을 통해서 따로 메모리를 배정받아야한다. name에 문자를 저장할 수 없기 때문에 scanf에서 에러가 발생한다.

char* get()
{
     char *name;

     printf("Input name : ");
     scanf("%s", name);

     return name;
}

또 이런 것도 에러.

char* get()
{
     char *name = "abc";

     printf("Input name : ");
     scanf("%s", name);

     return name;
}

이와 같이 할 경우는 우선 메모리 어딘가에 "abc"를 저장하고 그 주소를 name에 대입한다. "abc"는 string literal로 취급한다. 즉, 이렇게 초기화된 경우 name의 값을 바꿀 수 없게 된다.

이중포인터의 활용

scope를 벗어낫다고 malloc으로 할당받은 메모리가 해제되지는 않는다는 것이다.

#include <stdio.h>

int main(){
    int *p;

    {
        p = malloc(80);
    }

    *p = 1;
    printf("%d\n", *p);
}

아래는 오류가 난다.

#include <stdio.h>

void func(int *p){
    p = malloc(80); // malloc으로 복사된 p의 값을 새로 바꿔주는 것 뿐이다. 원래의 argument를 바꿔주는 것이 아니다.
}

int main(){
    int *p;

    func(p);

    *p = 1;
    printf("%d\n", *p);
}

이중 포인터를 활용해서 해결할 수 있다. 이런 경우를 위해서 이중포인터가 존재한다.

#include <stdio.h>

void func(int **p){
    *p = malloc(80); 
}

int main(){
    int *p;

    func(&p);

    *p = 1;
    printf("%d\n", *p);
}

아예 실제로 메모리 주소의 참조가 어떻게 되는지 출력해보자.

#include <stdio.h>

void func(int *p){
    printf("In the function\n");
    printf("%x\n", &p);
    printf("%x\n", p);
}

int main(){
    int *p;

    func(p);

    printf("\n");
    printf("In the main function\n");
    printf("%x\n", &p);
    printf("%x\n", p);
}

출력은 아래와 같다.

In the function
3ad55cc8 // 복사된 p의 주소
3ad55dd0 // p가 담고있는 값

In the main function
3ad55ce0 // 원래 p의 주소
3ad55dd0 // p가 담고있는 값

즉 p가 가지고 있는 주소값 자체는 잘 복사되서 전달되지만, func()에 p가 인자로 전달되면서 새로운 복사된 p가 메모리에 새로 배정받는다. 그리고 거기에 있는 값에 영향을 줘봤자, main에 있는 p의 값을 바꿔주진 않는다.


그럼 구조체 연산에 대해선 어떨까

#include<stdio.h>

typedef struct Test{
    int a;
    int *str;
} Test;

#include <stdio.h>

void func(Test *t){

    t->str = malloc(80);
    (t->str) = 1;
    printf("In the function\n");
    printf("%x\n", &t);
    printf("%x\n", t);
    printf("%x\n", &(t->a));
    printf("%x\n", t->a);
    printf("%x\n", &(t->str));
    printf("%x\n", t->str);
}

int main(){
    Test *t = malloc(sizeof(Test));

    func(t);

    printf("\n");
    printf("In the main function\n");
    printf("%x\n", &t);
    printf("%x\n", t);
    printf("%x\n", &(t->a));
    printf("%x\n", t->a);
    printf("%x\n", &(t->str));
    printf("%x\n", t->str);
}

출력은 다음과 같다.

In the function
e4796a18
53ee4260
53ee4260
0
53ee4268
1

In the main function
e4796a40
53ee4260
53ee4260
0
53ee4268
1

보면 func()에서는 아예 넘겨받은 t에 대해서 새로운 struct를 생성해주는 것을 알 수 있다.


구조체에서 ->은 *(구조체).과 동등하다고 배웠으니,구조체의 주소를 parameter로 넘겨받아도 ->를 통해서 정확한 멤버에 접근할 수 있다.

#include<stdio.h>

typedef struct Matrix{
    // row and coloum
    int n;
    int m;

    // data of matrix
    char ** matData;
    char textMat[TEXT_LENGTH];
} Matrix;

Matrix *initializeMatrix(Matrix * matStruct){
    matStruct->matData = malloc(sizeof(char*) * matStruct->n);
    for(int i = 0; i < matStruct->n; i++){
        matStruct->matData[i] = calloc(matStruct->m, sizeof(float) * matStruct->m);
    }

    matStruct->matData[0][0] = 1;
    matStruct->textMat[0] = 1;

    printf("%x\n", &matStruct);
    printf("%x\n", matStruct);
    printf("%x\n", &(matStruct->matData));
    printf("%x\n", matStruct->matData);
    printf("%x\n", matStruct->matData[0][0]);
    printf("%x\n", &(matStruct->textMat));
    printf("%x\n", matStruct->textMat);
    printf("%x\n", matStruct->textMat[0]);

    return matStruct;
}

int main(){
    Matrix * matStruct;

    initializeMatrix(matStruct);

    printf("\n\n", &matStruct);

    printf("%x\n", &matStruct);
    printf("%x\n", matStruct);
    printf("%x\n", &(matStruct->matData));
    printf("%x\n", matStruct->matData);
    printf("%x\n", matStruct->matData[0][0]);
    printf("%x\n", &(matStruct->textMat));
    printf("%x\n", matStruct->textMat);
    printf("%x\n", matStruct->textMat[0]);


    return 0;
}

출력은 아래와 같다.

9b8fc98
9b8fdc0
9b8fdc8
80cd3260
1
9b8fdd0
9b8fdd0
1

9b8fcd0
9b8fdc0
9b8fdc8
80cd3260
1
9b8fdd0
9b8fdd0
1

https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=dw5637&logNo=100043598497
https://chanywa.com/343
https://thrillfighter.tistory.com/124

  1. NULL, \0, NUL, 0 의 차이

나보다 이분이 더 잘 설명해주시지 않을까?

https://code4human.tistory.com/116

  1. 디버거 꼭 쓰자. 디버거를 얼마나 잘 쓰는지가 프로그램의 완성 시간을 가른다.
반응형

'개발 기록 > 행렬 계산기' 카테고리의 다른 글

행렬 계산기 개발 일지 - 8  (0) 2021.05.30
행렬 계산기 개발 일지 - 7  (0) 2021.05.28
행렬 계산기 개발 일지 - 6  (0) 2021.05.23
행렬 계산기 5일차  (0) 2021.05.12
행렬계산기 개발 4일차...  (0) 2021.05.03