ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • C언어 문법 정리 (배열, 포인터)
    언어/C 2023. 7. 4. 21:53
    728x90
    반응형
    SMALL

    SAFFY과정을 임베디드로 입과하게 되어 C언어 기본에 대해 공부해보려고 합니다.

    기본 타입이나 연산자, 제어문, 함수 등 정말 기본적인 부분에 대해서는 다루지 않으려고 합니다.

    이번 포스팅부터 배열, 메모리 관리, 구조체, 컴파일 등과 같은 C언어를 다루기 위해 필요한 기본 문법에 대해 정리하도록 하겠습니다.

     

     

    1차원 배열

    배열은 같은 타입의 변수들로 이루어진 집합입니다.

    배열을 구성하는 각각의 값을 요소(element)라고 합니다. 해당 요소를 가리킬 수 있는 위치를 인덱스(index)라고 합니다.

    C언어와 같은 경우 배열의 인덱스는 0부터 시작하며, 인덱스는 0을 포함한 양의 정수로만 이루어져 있습니다.

     

    기본 선언 방식

    <타입> 변수명[<배열 길이>];

    타입 : 배열의 각 원소의 타입을 명시합니다.

    배열 길이 : 배열을 선언할 경우 해당 배열의 길이를 미리 명시합니다.

     

    선언과 동시에 초기화하는 방법

    <타입> 변수명[<배열 길이>] = {요소1, 요소2, ...};

    여기서, 초기화 리스트의 타입과 배열의 타입은 일치해야 합니다.

    선언과 동시에 초기화를 해준다면 배열 길이는 생략되어도 자동으로 설정됩니다.

     

    배열 사용 시 주의할 점

    C 컴파일러는 배열의 길이를 전혀 신경 쓰지 않습니다. 길이가 3인 배열 human이 선언하고 human[3]='cotton'으로 4번째 인덱스에 값을 넣어주어도 컴파일러는 human[3]을 인식하게 됩니다.

    그러므로, 배열에 인덱스로 접근할 경우 배열의 길이 등을 검사하여 오류를 방지해주는 것이 좋습니다.

     

     

     

    다차원 배열

    다차원 배열이란, 2차원 이상의 배열을 의미합니다. 배열 요소로 또는 다른 배열을 가지는 배열을 의미합니다.

     

    2차원 배열

    선언 방법

    <타입> 변수명[<행 길이>][<열 길이>];

    선언과 동시에 초기화

    int arr1[2][3] = {10,20,30,40,50,60};
    // arr1[0] = [10,20,30];
    // arr1[1] = [40,50,60];
    
    int arr2[3][2] = {10,20,30,40,50,60};
    // arr2[0] = [10,20];
    // arr2[1] = [30,40];
    // arr2[2] = [50,60];
    
    int arr3[2][3] = {
    	{10,20,30},
        {40,50,60}
    };

    선언과 동시에 초기화 해줄 경우 두 가지 방법이 있습니다.

    • {요소[0][0],요소[0][1], ... , 요소[1][0], 요소[1][1], ... } 와 같은 형식으로 초기화 할 수 있습니다.
    • { {요소[0][0], 요소[0][1], ... } , {요소[1][0],요소[1][1], ... } , ... } 와 같은 형식으로 초기화 할 수 있습니다.

    1차원 배열과 동일하게 배열의 길이를 명시하지 않아도 초기화에 따라 자동 설정이 될 수 있습니다. (단, 컴파일러가 명시적으로 길이를 알 수 있을 경우에만 자동 설정이 가능합니다.)

     

     

     

    주소값

    데이터의 주소값이란 해당 데이터가 저장된 메모리의 시작 주소를 의미합니다.

    C언에서는 주소값을 1바이트 크기로 메모리 공간을 나누어 표현합니다. (int형 데이터는 4바이트의 크기를 가집니다.)

     

    포인터

    메모리의 주소값을 저장하는 변수입니다.

    int n=40;
    int *ptr=&n; // 포인터 변수

    &주소 연산자로 변수의 이름 앞에 사용되며, 해당 변수의 주소값을 반환합니다. 앰퍼샌드(ampersand)라고 읽으며, 번지 연산자라고도 불립니다.

    *참조 연산자로 포인터의 이름이나 주소 앞에 사용, 포인터가 가리키는 주소에 저장된 값을 반환합니다.

    포인터를 선언한 뒤 참조 연산자를 사용하기 전에 반드시 포인터는 초기화되어 있어야 합니다.

     

    포인터 연산 규칙

    1. 포인터끼리의 덧셈, 곱셈, 나눗셈은 의미가 없습니다.

    2. 포인터끼리의 뺄셈은 두 포인터의 상대적 거리를 나타냅니다.

    3. 포인터에 정수를 더하거나 뺄 수 있지만, 실수 연산은 허용되지 않습니다.

    4. 포인터끼리 대입하거나 비교할 수 있습니다.

     

     

     

    값에 의한 전달(call by value)

    인자로 전달된 변수가 가지고 있는 값을 함수 내의 매개변수에 복사하는 방식입니다.

    복사된 매개변수는 인수로 전달된 변수와 전혀 다른 별개의 변수가 됩니다.

    따라서, 함수 내 매개변수의 변형은 인자로 전달된 변수에는 아무런 영향을 미치지 않습니다.

    void add(int num2) {
    	num2+=10;
    }
    
    int main(void){
    	int num1=10;
        
    	add(num1);
    	printf("%d",num1); // 10출력
        
    	return 0;
    }

    위와 같이 add함수의 매개변수 num2은 num1을 인자로 받아 10을 더해주지만, num2와 num1은 전혀 다른 변수이므로 num2에 10을 더한 것은 num1에 영향을 미치지 않습니다.

     

    참조에 의한 전달(call by reference)

    인자로 변수의 값이 아닌 주소값을 전달하는 방식입니다.

    이러한 방식으로 전달된 인자값은 함수 내에서 변경 가능합니다.

    void add(int* num2) {
    	*num2+=10;
    }
    
    int main(void) {
    	int num1=10;
        
    	add(num1);
    	printf("%d",num1); // 20출력
        
    	return 0;
    }

    위와 같이 add함수에 num1의 주소값을 인자로 전달하면 매개 변수 num2는 num1의 주소값을 전달 받게 되고, num2에서 해당 주소값에 저장된 값을 가져와 변경시키므로 인자로 전달한 num1의 값이 변경됩니다.

     

     

     

    포인터의 포인터

    포인터 변수를 가리키는 포인터를 의미합니다.

    참조 연산자(*)를 두 번 사용하여 표현하며, 이중 포인터라고도 불립니다.

    int num=10;
    int* ptr = &num;
    int** ptr_ptr = &ptr;

     

    void 포인터

    일반적인 포인터와 다르게 타입을 명시하지 않은 포인터입니다.

    따라서, 어떠한 변수나 함수, 포인터 값을 가리킬 수 있지만, 포인터 연산이나 메모리 참조와 같은 작업은 할 수 없습니다.

    즉, 주소값을 저장하는 용도로 밖에 쓰이지 못합니다. void포인터를 사용할 경우 해당 타입으로 명시적 타입 변환 작업을 거친 후 사용합니다.

     

    함수 포인터

    프로그램에 정의된 함수는 프로그램이 실행될 때 모두 메인 메모리에 올라가게 됩니다. 이 때 , 함수의 이름은 메모리에 올라간 함수의 시작 주소를 가리키는 포인터 상수가 됩니다. 이러한 포인터 상수를 함수 포인터라고 합니다.

    함수 포인터의 타입은 반환값과 매개변수에 의해 정의 됩니다. -> 함수의 원형을 알아야지만 해당 함수에 맞는 함수 포인터를 만들 수 있습니다.

    void function(int, int); // 함수 원형
    
    void (*ptr_function)(int,int); // 함수 포인터

    함수 포인터는 함수를 다른 함수에 전달할 때 유용하게 사용됩니다.

     

    Null pointer

    0이나 Null 값으로 초기화한 포인터를 널 포인터라고 합니다.

     

     

     

    포인터와 배열

    배열의 이름은 해당 값을 바꿀 수 없다는 점을 제외하면 포인터와 같습니다.

    그러므로 배열을 포인터 상수라고 합니다.

    int arr[3] = {1,2,3};
    int* ptr = arr;
    
    printf("%d",ptr[0]); // 1 출력

    위와 같이 선언된 배열의 이름을 포인터 변수에 대입해주면 해당 배열에 접근할 수 있습니다.

    하나 다른 점은 각 변수의 size입니다. 포인터 변수는 해당 포인터 변수 자체의 크기를 출력하지만, 배열은 해당 배열의 크기가 출력됩니다.

     

    배열의 포인터 연산

    배열을 포인터처럼 사용할 경우 다음과 같이 사용할 수 있습니다.

    int arr[3] = {1,2,3};
    
    printf("%d",*(arr+1)); // 2 출력

    배열의 이름에 정수를 더해주고 참조 연산자로 접근할 경우 해당 인덱스의 원소를 반환해줍니다.

    이와 같이 접근할 경우, 배열의 길이를 넘어서지 않도록 주의해야 합니다.

     

    포인터 배열

    배열의 요소로 포인터를 가지는 배열

    int num1=1, num2=2, num3=3;
    int* arr[] = {&num1,&num2,&num3};
    
    printf("%d",*arr[0]); // 1 출력

     

    배열 포인터

    배열을 가리키는 포인터를 의미합니다.

    앞서 배열 이름은 해당 값을 바꿀 수 없다는 점을 제외하고 포인터와 같다고 했습니다.

     

    배열 이름을 포인터처럼 사용하고, 포인터를 배열처럼 사용할 수 있는 이유는 2차원 이상의 배열을 가리킬 경우 포인터를 통해 인덱싱을 하기 위해서 입니다.

    -> 배열 포인터는 1차원일 경우 의미가 없고 2차원 이상의 배열에서만 의미가 있습니다.

    int arr[2][3] = {
        {1, 2, 3},
        {4, 5, 6}
    };
    
    printf("%d",*arr[0]); // 1 출력
    printf("%d",*arr[1]); // 4 출력

    2차원 배열의 포인터 연산 시 행의 길이만큼 연산이 되는 것을 확인할 수 있습니다.

     

    위의 arr는 아래와 같은 배열 포인터로 선언할 수 있습니다.

    int (*ptr)[3] = arr;

     

    main 함수의 argc, argv

    int main(int argc, char (*argv)[]);

    main 함수 선언 시 두 개의 인자가 존재할 수 있습니다.

    첫 번째 인자는 main함수에 전달되는 문자열의 개수를 명시합니다.

    두 번째 인자는 main함수에 전달되는 문자열이 저장된 배열입니다.

    반응형
    LIST

    댓글

Designed by Tistory.