[C++] 배열과 매개 변수, 함수에 대한 간단한 고찰



첫번째 실험. 1차원 배열을 매개 변수로 넘기기




//
//  arrayTest.cpp
//  BJ
//
//  Created by 신기열 on 21/10/2019.
//  Copyright © 2019 신기열. All rights reserved.
//

#include <stdio.h>
#include <iostream>

using namespace std;

// array[] 형식으로 넘기기
void Array(int a[]){

    cout << "------------------- aaray[]로 넘긴 배열 a -------------------" << '\n';
    int i = 0;
    while(i != 12){
        printf("a[%d]의 값 : %d      ", i, a[i]);
        printf("a[%d]의 주솟값 : %x\n", i, &a[i]);
        i++;
    }
    cout << "----------------------------------------------------------" << '\n';
}

// *array 형식으로 넘기기
void Array2(int *a){
    
    cout << "-------------------- *array로 넘긴 배열 a --------------------" << '\n';
    int i = 0;
    while(i != 12){
        printf("a[%d]의 값 : %d      ", i, a[i]);
        printf("a[%d]의 주솟값 : %x\n", i, &a[i]);
        i++;
    }
    cout << "----------------------------------------------------------" << '\n';
}

// array[10] 형식으로 넘기기
void Array3(int a[0]){
    
    cout << "------------------ array[10]로 넘긴 배열 a ------------------" << '\n';
    cout << " 이 경우 array[0], array[1] ... array[10]으로 넘길 때 모두 같은" << '\n';
    cout << " 결과가 나온다." << '\n';
    int i = 0;
    while(i != 12){
        printf("a[%d]의 값 : %d      ", i, a[i]);
        printf("a[%d]의 주솟값 : %x\n", i, &a[i]);
        i++;
    }
    cout << "----------------------------------------------------------" << '\n';
}

int main(){
    
    int a[10];
    for(int i = 0; i < 10; i++){
        a[i] = i;
    }
    
    cout << "------------------- main 함수에서의 배열 a -------------------" << '\n';
    int i = 0;
    while(i != 12){
        printf("a[%d]의 값 : %d      ", i, a[i]);
        printf("a[%d]의 주솟값 : %x\n", i, &a[i]);
        i++;
    }
    cout << "----------------------------------------------------------" << '\n';
    Array(a);
    Array2(a);
    Array3(a);
    
}



array[], *array, array[10], array[0]으로 넘겼을 때 모두 같은 결과가 나왔다. 어떤 방식으로 하든 일단 주솟값을 넘기는 것이므로 똑같이 나오게 된다.
a 배열은 10개의 값을 가지고 있어서, 범위를 초과하면 당연히 쓰레기 값이 출력된다.  그렇다면 매개변수로 넘긴 배열의 index의 범위를 알고 싶다면? 그런거 없다. 배열의 범위를 애당초 알고 있거나, 배열에 들어갈 수 있는 수의 조건을 알고 있거나, 가변 길이를 원한다면 벡터를 사용하도록 하자. 템플릿을 수정해서 직접 구현하는 방법이 있다는데, 이 경우는 벡터를 쓰는 것과 다를 바가 없다. 벡터를 쓰자!






두번째 실험. 다차원 배열을 매개 변수로 넘기기




//
//  arrayTest2.cpp
//  BJ
//
//  Created by 신기열 on 21/10/2019.
//  Copyright © 2019 신기열. All rights reserved.
//

#include <stdio.h>
#include <iostream>

using namespace std;

void Array(int a[][3]){
    
    cout << "------------------- array[][3]로 넘긴 배열 a -------------------" << '\n';
    int i = 0;
    while(i != 4){
        int j = 0;
        while(j != 4){
            printf("a[%d][%d]의 값 : %d      ", i, j, a[i][j]);
            printf("a[%d][%d]의 주솟값 : %x\n", i, j, &a[i][j]);
            j++;
        }
        i++;
    }
    cout << "----------------------------------------------------------" << '\n';
    
}

void Array2(int (*a)[3]){
    
    cout << "------------------- (*array)[3]로 넘긴 배열 a -------------------" << '\n';
    int i = 0;
    while(i != 4){
        int j = 0;
        while(j != 4){
            printf("a[%d][%d]의 값 : %d      ", i, j, a[i][j]);
            printf("a[%d][%d]의 주솟값 : %x\n", i, j, &a[i][j]);
            j++;
        }
        i++;
    }
    cout << "----------------------------------------------------------" << '\n';
    
}

void Array3(int a[3][3]){
    cout << "------------------- array[3][3]로 넘긴 배열 a -------------------" << '\n';
    int i = 0;
    while(i != 4){
        int j = 0;
        while(j != 4){
            printf("a[%d][%d]의 값 : %d      ", i, j, a[i][j]);
            printf("a[%d][%d]의 주솟값 : %x\n", i, j, &a[i][j]);
            j++;
        }
        i++;
    }
    cout << "----------------------------------------------------------" << '\n';
    
}

void Array4(int a[][]){

    cout << "------------------- array[][]로 넘긴 배열 a -------------------" << '\n';
    cout << "컴파일 불가능" << '\n';
    int i = 0;
    while(i != 4){
        int j = 0;
        while(j != 4){
            printf("a[%d][%d]의 값 : %d      ", i, j, a[i][j]);
            printf("a[%d][%d]의 주솟값 : %x\n", i, j, &a[i][j]);
            j++;
        }
        i++;
    }
    cout << "----------------------------------------------------------" << '\n';

}

void Array5(int (*a)[]){
    cout << "------------------- (*array)[]로 넘긴 배열 a -------------------" << '\n';
    cout << "컴파일 불가능" << '\n';
    int i = 0;
    while(i != 4){
        int j = 0;
        while(j != 4){
            printf("a[%d][%d]의 값 : %d      ", i, j, a[i][j]);
            printf("a[%d][%d]의 주솟값 : %x\n", i, j, &a[i][j]);
            j++;
        }
        i++;
    }
    cout << "----------------------------------------------------------" << '\n';
    
}

void Array6(int (*a)[3][4]){
    cout << "------------------- (*array)[3][4]로 넘긴 배열 a -------------------" << '\n';
    cout << "컴파일 불가능" << '\n';
    int i = 0;
    while(i != 2){
        int j = 0;
        while(j != 3){
            int k = 0;
            while(k != 4){
                printf("a[%d][%d][%d]의 값 : %d      ", i, j, k, a[i][j][k]);
                printf("a[%d][%d][%d]의 주솟값 : %x\n", i, j, k, &a[i][j][k]);
                k++;
            }
            j++;
        }
        i++;
    }
    cout << "----------------------------------------------------------" << '\n';
    
}

int main(){
    
    int a[3][3];
    int a2[2][3][4];
    
    for(int i = 0; i < 3; i++){
        for(int j = 0; j < 3; j++){
            a[i][j] = i + j;
        }
    }
    
    for(int i = 0; i < 2; i++){
        for(int j = 0; j < 3; j++){
            for(int k = 0; k < 4; k++){
                a2[i][j][k] = i + j + k;
            }
        }
    }
    
    Array(a);
    Array2(a);
    Array3(a);
    Array4(a);
    Array5(a);
    Array6(a2);
    
    return 0;
}


일단 Array4와 Array5 에서 오류가 발생하여 컴파일이 안된다. (둘을 빼고 컴파일을 하면 1차원 배열과 같이 같은 값, 같은 주솟값으로 잘 나오게 된다.) 오류 내용은 다음과 같다.

Array4



Array5



Array has incomplete element type 'int[]'

SubScript of pointer to incomplete type 'int []'
제대로 된 타입이 아닌 형태를 이용했다는 것인데, 배열을 매개 변수로 보내는 기본적인 형태는 *array이다. 

배열은 변수들의 그룹으로 특정 저장 공간 array로부터 type의 크기만큼 일정 간격으로 공간을 차지하고 있는 것인데, 2차원 배열에서의 (*array)[size]는 각각의 주소 공간에서 하나의 주소 공간이 size * type만큼 확보하고 있는 것이다. 여기서 포인터, 즉 주솟값을 넘기는 것은 배열의 값을 사용하면서, 수정도 하겠다는 것을 선언하는 것이다.

이 때 C나 C++ 언어에서는 array[]나 array[][size] 같은 형태도 허용하는데, 매개 변수를 배열 형태로 보낸다고 해도 배열의 포인터로써 사용하겠다는 의미이며, 이 때 대괄호 안에 어떤 수가 들어가든 대괄호의 내용은 그냥 무시한다. 

(어차피 배열의 포인터를 쓰고자하는 의미니까) 그래서 1차원 배열에서는 *arr = arr[] = arr[0] = arr[1] = ... = arr[10]이 다 같은 결과가 나오는 것이다. 그렇다면 2차원 배열에서의 array[][]는? 
array의 포인터를 이용하겠다는 의미는 통할지라도, 그 그룹 안에 얼마 만큼 공간이 들어가는지를 선언해주지 않았기 때문에 오류가 나오는 것이다. 

가령,
int array[][4] = [[int], [int], [int], [int]][[int], [int], [int], [int]].... 이런 식으로 되지만,
int array[][] = [[int], [int], [int], [int]......][[int], [int], [int], [int]......] 이런 식으로 되기 때문에 안에 들어가는 공간을 특정할 수 없기 때문에 불가능한 것이다. 사실상 Array4 와 Array5에서 생기는 오류는 같은 의미를 가지게 된다. 

그렇다면 3차원 배열을 어떻게 매개변수로 넘겨주는가? 조금 응용해보면 알 수 있다. (*array)[size][size]와 같이 안에 들어가는 차원들의 size를 정해준 후 넘겨주면 되는 것이다.






세번째 실험. 배열과 재귀함수





//
//  arrayTest3.cpp
//  BJ
//
//  Created by 신기열 on 21/10/2019.
//  Copyright © 2019 신기열. All rights reserved.
//

#include <stdio.h>
#include <iostream>

using namespace std;

void recursive1(int *a1, int cnt){
    if(cnt == 3) return;
    for(int i = 0; i < 5; i++){
        a1[i] = a1[i] + a1[i];
    }
    for(int i = 0; i < 5; i++){
        cout << a1[i] << " ";
    }
    cout << '\n';
    recursive1(a1, cnt + 1);
}

void recursive2(int (*a2)[4], int cnt){
    if(cnt == 3) return;
    for(int i = 0; i < 4; i++){
        for(int j = 0; j < 4; j++){
            a2[i][j] = a2[i][j] + a2[i][j];
        }
    }
    for(int i = 0; i < 4; i++){
        for(int j = 0; j < 4; j++){
            cout << a2[i][j] << " ";
        }
        cout << '\n';
    }
    cout << '\n';
    recursive2(a2, cnt + 1);
}

void recursive3(int (*a3)[3][3], int cnt){
    if(cnt == 3) return;
    for(int i = 0; i < 3; i++){
        for(int j = 0; j < 3; j++){
            for(int k = 0; k < 3; k++){
                a3[i][j][k] = i + j + k;
            }
        }
    }
    for(int i = 0; i < 3; i++){
        for(int j = 0; j < 3; j++){
            for(int k = 0; k < 3; k++){
                cout << a3[i][j][k] << " ";
            }
            cout << '\n';
        }
        cout << '\n';
    }
    cout << '\n';
    recursive3(a3, cnt + 1);
}

int main(){
    
    int a1[5];
    int a2[4][4];
    int a3[3][3][3];
    
    for(int i = 0; i < 5; i++){
        a1[i] = i;
    }
    
    for(int i = 0; i < 4; i++){
        for(int j = 0; j < 4; j++){
            a2[i][j] = i + j;
        }
    }
   
    for(int i = 0; i < 3; i++){
        for(int j = 0; j < 3; j++){
            for(int k = 0; k < 3; k++){
                a3[i][j][k] = i + j + k;
            }
        }
    }
    cout << "----------------1차원 배열-------------" << '\n';
    recursive1(a1, 0);
    cout << "=====================================" << '\n';
     cout << "----------------2차원 배열-------------" << '\n';
    recursive2(a2, 0);
      cout << "=====================================" << '\n';
        cout << "----------------3차원 배열-------------" << '\n';
    recursive3(a3, 0);
      cout << "=====================================" << '\n';
    
    
    return 0;
}


일단 1차원이든 2차원이든 3차원이든 간에, 혹은 배열을 재귀함수 내에서 선언해서 돌리든, 밖에 미리 배열을 생성해서 돌리든 이 또한 별 문제는 없다. 결국 배열의 차원과 재귀함수는 큰 관계가 없었다. 그렇다면 그 때 왜 segmentation fault가 났는가하면 재귀함수의 호출의 횟수 때문이었다.

호출 횟수가 10000번 정도면 상관이 없어지지만, 10만번이 넘어가게 되면 스택에 들어가있는 함수의 개수가 너무 많아지기 때문에 stack overflow가 발생하는 것이었다. 이래서 DFS를 사용할 때 깊이가 너무 깊어지지 않은지를 잘 생각해봐야한다.

댓글

  1. 배열 넘기는 법이 정립된 것 같네요 감사합니다!

    답글삭제
    답글
    1. 댓글을 이제 봤네요.. 읽어주셔서 감사드립니다 ^^

      삭제

댓글 쓰기