brunch

매거진 Sinclair

You can make anything
by writing

C.S.Lewis

by Sinclair Feb 12. 2016

배열과 포인터: X-files 배열

배열 탐험 신비의 세계, 진실은 저 너머에 IV


배열 활용하기  



같은 함수 안에서 배열을 사용하는 것은 거의 아무런 문제가 없습니다. 배열을 포인터처럼 사용하는 것도 가능합니다.

그냥 예전처럼 바둑판으로 생각해도 거의 문제가 없습니다.

하지만 함수 하나로 프로그램을 만드는 일은 없습니다.

요즘은 hello world가 아닌 다음에야 대부분 수 십, 수 백 개 이상의 함수들이 모여서 하나의 프로그램을 이룹니다. 현재 사용하던 배열을 다른 함수의 인자로 넘겨 주거나 때론 내가 사용한 배열을 호출한 쪽으로 리턴 해야 할 경우가 있습니다.


우선 먼저 말하자면 배열은 절대로 그냥 리턴 해서는 안됩니다.

그리고 배열은 항상 사이즈와 함께 사용해야 합니다.

절대 단독으로 사용하면 안됩니다.


자바 등, OOP언어라면 모든 배열이 객체 이므로 사이즈라는 속성을 항상 달고 다닙니다. 하지만 C언어에서 배열은 연산자 입니다. 그리고 수식에서 사용할 때 범위와 관련된 어떤 동작도 하지 않습니다. 때문에 직접 범위체크를 하려면 반드시 사이즈를 알고 있어야 합니다. &와 sizeof 단항 연산자를 만날 때를 제외하고는 배열 이름은 시작 주소라고 했습니다.


배열 이름을 넘겼으니 전체 배열이 아닌 시작 주소만 함수로 넘어 갑니다. 때문에 호출한 함수에서 아무리 배열 이름을 사용하여 사이즈를 알고 싶어도 주소 값의 사이즈만 OS(운영체제)에 따라 일정하게 나오게 됩니다.



#include <stdio.h>  

/*

* copyleft ⓛ 2006 - 2017 programmed by Sinclair

*/  

// prototypes

int printArray1st(int array[10]) ;

int printArray2nd(int * , int) ;  

int printMatrix1st(int matrix[3][4]) ;

int printMatrix2nd(int ** matrix) ; // 타입이 안 맞아 불가능

int printMatrix3rd(int (*)[4] , int) ;   


main() {  

    // int arr[] ; // 메모리 할당에 필요한 사이즈가 없으니 에러

    int arra[] = { 1,2,3,4,5,6,7,8,9,0 } ;

    int array[10] ;  

    // int m[][] ; // 메모리 할당에 필요한 사이즈가 없으니 에러

    // int ma[][4] ; // 메모리 할당에 필요한 사이즈가 없으니 에러

    int mat[][4] = { 1,2,3,4,5,6,7,8,9,10,11,12 } ;

    // int matr[3][] = { 1,2,3,4,5,6,7,8,9,10,11,12 } ; // Error!!

    int matrix1st[3][4] = { 1,2,3,4,5,6,7,8,9,10,11,12 } ;

    int matrix[3][4] = { {1,2,3,4},{5,6,7,8},{9,10,11,12} } ;

    int third[2][3][4] = { 0 } ;


    int i , j ; // for index


    for( i = sizeof array / sizeof * array ; i > 0 ; )

        printf("%d\t" , array[--i] = i * 10) ;

    puts("\n**************************************************");


    for( i = sizeof matrix / sizeof * matrix ; i > 0 ; )

        for( --i , j = sizeof * matrix / sizeof ** matrix ; j > 0 ; )

            printf("%d%c" , matrix[i][j] , --j%4 ? '\t' : '\n') ;     

    puts("**************************************************");


    printf("array : %d %d\n" , sizeof array , sizeof * array) ;

    printf("matrix : %d %d %d\n" , sizeof matrix ,

                        sizeof * matrix , sizeof ** matrix) ;

    printArray1st(array) ;

    printArray2nd(array , sizeof array / sizeof * array) ;

    printMatrix1st(matrix) ;

    printMatrix2nd(matrix) ; // 이렇게 하면 지대 난리나요

    printMatrix3rd(matrix , sizeof matrix / sizeof * matrix) ;

    printArray2nd( *matrix , sizeof matrix / sizeof ** matrix) ;

    // 일차원 배열 출력하는 함수로 이차원 배열도 출력 가능


    for( i = sizeof  third / sizeof *** third ; i > 0 ; )

        (**third)[--i] = i * i ;  


    printArray2nd( **third , sizeof third / sizeof *** third) ;

    // 일차원 배열 출력하는 함수로 삼차원 배열도 출력 가능

    for( i = sizeof third / sizeof ** third ; i > 0 ; )

        for( --i , j = sizeof ** third / sizeof *** third ; j > 0 ;)

            (*third)[i][--j] = i * j ;

            // third[0][i][j] : int third[6][4] 인 것처럼 동작  

    printArray2nd( **third , sizeof third / sizeof *** third) ;


    for( i = sizeof third / sizeof * third ; i > 0 ; )

        for( --i , j = sizeof * third / sizeof *** third ; j > 0 ; )

            (*third[i])[--j] = i * j ;

            // third[i][0][j] : int third[2][12] 인 것처럼 동작   

    printArray2nd( **third , sizeof third / sizeof *** third) ;  

    return 0 ;  

} // end main()   


int printArray1st(int array[10]) {  

    register i ;  

    puts("printArray1st()") ;

    printf("%d %d\n" , sizeof array , sizeof * array) ;

    for( i = 10 ; i > 0 ; )

        printf("%d\t" , array[--i]) ;

    putchar('\n') ;  

    return 0 ;  

} // end printArray1st()   


int printArray2nd(int * array , int size)

// int printArray2nd(int array[] , int size)

// int printArray2nd(int array[10] , int size)

// int printArray2nd(int array[100] , int size)

{  

    register i ;  

    puts("printArray2nd()") ;

    printf("%d %d\n" , sizeof array , sizeof * array) ;  

    for( i = size ; i > 0 ; )

        printf("%d\t" , array[--i]) ;

    putchar('\n') ;  

    return 0 ;  

} // end printArray2nd()   


int printMatrix1st(int m[3][4]) {  

    register i , j ;  

    puts("printMatrix1st()") ;

    printf("%d %d %d\n" , sizeof m , sizeof * m , sizeof ** m) ;  

    for( i = 3 ; i > 0 ; )

        for( --i , j = sizeof * m / sizeof ** m ; j > 0 ; )

            printf("%d%c" , m[i][j] , --j%4 ? '\t' : '\n') ;

    return 0 ;  

} //end printMatrix1st()   


int printMatrix2nd(int ** m) {  

    register i , j ;  

    puts("printMatrix2nd()") ;

    printf("%d %d %d\n" , sizeof m , sizeof * m , sizeof ** m) ;  

    for( i = 3 ; i > 0 ; )

        for( --i , j = 4 ; j > 0 ; )

            printf("%d%c" , m[i][j] , --j%4 ? '\t' : '\n') ;

            // 여기에서 치명적인 segmentation fault 에러가 납니다.

            // printf("%d%c" , *(*(m+i)+j) , --j%4 ? '\t' : '\n') ;

    return 0 ;  

} //end printMatrix2nd()   


int printMatrix3rd(int (*m)[4] , int size)

// int printMatrix3rd(int m[][4] , int size)

// int printMatrix3rd(int m[3][4] , int size)

// int printMatrix3rd(int m[100][4] , int size)

{  

    register i , j ;  

    puts("printMatrix3rd()") ;

    printf("%d %d %d\n" , sizeof m , sizeof * m , sizeof ** m) ;  

    for( i = size ; i > 0 ; )

        for( --i , j = sizeof * m / sizeof ** m ; j > 0 ; )

            printf("%d%c" , m[i][j] , --j%4 ? '\t' : '\n') ;

            // printf("%d%c" , (*(m+i))[j] , --j%4 ? '\t' : '\n') ;

           // printf("%d%c" , *(*(m+i)+j) , --j%4 ? '\t' : '\n') ;  

    return 0 ;  

} //end printMatrix3rd()    




어때요? 멋지지 않습니까? 이렇게 보고도 못 믿겠습니까? 지금 우리는 어쩌면 진실을 외면한 채 우리가 보고 싶은 것 만 보고 있는지도 모르겠습니다.

만일 배열이 바둑판 식 행렬이라면 이런 코드가 어떻게 가능하겠습니까?

이처럼 배열은 연산자이며 메모리를 핸들링 하는 다양한 방법을 제공합니다.


더욱이 일반 수식에서는 포인터와 완벽하게 호환이 되고 있습니다.

일반적으로 배열 연산자가 보다 논리적이고 인간중심이라고 말합니다. 일반 수식에서 때로 배열을 포인터처럼 사용하고 복잡하게 엮인 포인터를 배열로 사용하는 일은 아주 멋진 일입니다.


그렇다면 배열이 포인터일까요? 아니면 포인터가 배열일까요? 그 질문에 대한 대답은 배열은 배열일 뿐 포인터가 아니고 포인터 역시 포인터일 뿐 배열이 될 수 없다는 것입니다.

다만 어떤 컴파일러, 어떤 OS, 어떤 CPU에서는 배열연산자 보다 포인터 연산자가 좀더 빠르게 동작하기도 합니다.

앞에서 얼핏 정리3번과 함께 등장하고 있는 포인터에 대해 좀더 알아 보도록 하겠습니다.


우리가 앞에서 함께 공부했던 정리 3번 기억하죠? 이거 정말 중요한 녀석입니다.



Sinclair's 정리 #3 ( 포인터의 정체 )

// 값이 아닌 타입이 같다는 의미!!!


양방향 모두 성립하는 수식입니다.

1) 어떤 type의 변수(레지스터 제외) typeData가 존재할 때 그 변수의 주소 값 &typeData의 타입은 type *형이다.

2) type * 형은 type형 데이터의 주소 값을 담기 위한 타입이다.









C언어 및 기타 프로그래밍 관련 질문은 오픈 카톡으로

group talk - https://is.gd/yourc

1:1 talk - https://is.gd/aboutc

#Sinclair #씽클레어 #싱클레어 #씽클레어도씨 #씨언어 #씨프로그래밍  #C언어 #Cprogramming #C_Programming #C #Programming #Clanguage #C_Language

매거진의 이전글 배열과 포인터: X-files 포인터
브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari