본문 바로가기
IT & AI/AI 지식

C 언어 변수, 다차원 배열, 응용 포인터 정리

by 빛나는해커 2024. 11. 19.

1.  변수의 영역과 데이터 공유

 

변수 사용 영역

  • 지역 변수는 함수나 블록 내에서 선언되고 사용되는 변수로, 해당 블록이 종료되면 메모리에서 사라진다.
  • 지역 변수는 함수나 코드 블록 내에서만 접근할 수 있어 데이터의 가시성을 제한하는 데 유용하다.
void example() {
    int localVar = 10;  // 지역 변수
    printf("지역 변수: %d\n", localVar);
}
  • 지역 변수는 {}로 감싸진 블록 안에서만 유효하다.
  • 중첩된 블록에서 동일한 이름의 변수를 선언하면 블록 안의 변수가 우선적으로 사용된다.
void nestedBlockExample() {
    int num = 5;
    {
        int num = 10;  // 중첩된 블록 내의 지역 변수
        printf("중첩 블록 내 num: %d\n", num);  // 출력: 10
    }
    printf("외부 블록 내 num: %d\n", num);  // 출력: 5
}
  • 전역 변수는 함수 외부에서 선언된 변수로, 프로그램 전체에서 접근이 가능하다.
  • 전역 변수는 메모리에서 프로그램이 종료될 때까지 유지된다.
int globalVar = 20;  // 전역 변수

void printGlobalVar() {
    printf("전역 변수: %d\n", globalVar);
}
  • 정적 지역 변수는 static 키워드를 사용하여 선언되며, 함수가 호출될 때마다 초기화되지 않고 이전 값을 유지한다.
  • 이러한 변수는 블록 내에서만 접근할 수 있지만, 메모리 상에서는 프로그램 종료 시까지 유지된다.
void staticExample() {
    static int count = 0;  // 정적 지역 변수
    count++;
    printf("count: %d\n", count);
}
  • 레지스터 변수는 CPU의 레지스터에 저장되기를 요청하는 변수로, 빠른 접근이 필요할 때 사용한다.
  • 컴파일러는 레지스터에 저장할지 여부를 결정하므로, 반드시 레지스터에 저장된다고 보장할 수는 없다.
void registerExample() {
    register int i = 0;
    for (; i < 10; i++) {
        printf("%d ", i);
    }
}

 

함수의 데이터 공유 방법

  • 함수에 인수를 전달할 때 기본적으로 값이 복사되어 전달된다.
  • 이 경우 함수 내에서 매개변수의 값을 변경해도 원본 변수에는 영향을 주지 않는다.
void passByValue(int a) {
    a = 20;
}

int main() {
    int num = 10;
    passByValue(num);
    printf("num: %d\n", num);  // 출력: 10 (값이 변경되지 않음)
    return 0;
}
  • 주소를 전달하면 함수에서 직접 원본 변수의 값을 변경할 수 있다.
  • 이를 포인터를 통해 구현한다.
void passByReference(int *p) {
    *p = 20;
}

int main() {
    int num = 10;
    passByReference(&num);
    printf("num: %d\n", num);  // 출력: 20 (값이 변경됨)
    return 0;
}
  • 함수에서 주소를 반환하여, 다른 함수에서도 해당 메모리의 값을 참조할 수 있다.
  • 단, 함수 내부에서 선언된 지역 변수의 주소를 반환하면 안 된다.
  • 이는 함수가 종료되면서 메모리에서 사라지기 때문이다.
int* returnPointer(int *a) {
    *a += 10;
    return a;
}

int main() {
    int num = 30;
    int *p = returnPointer(&num);
    printf("num: %d\n", *p);  // 출력: 40
    return 0;
}

2.  다차원 배열과 포인터 배열

 

다차원 배열

  • 2차원 배열은 행과 열로 이루어져 있으며, 선언할 때 각 차원의 크기를 지정해야 한다.
int matrix[3][3];

for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {
        matrix[i][j] = i * j;
        printf("matrix[%d][%d] = %d\n", i, j, matrix[i][j]);
    }
}
  • 2차원 배열을 선언과 동시에 초기화할 수 있다.
int matrix[2][3] = {
    {1, 2, 3},
    {4, 5, 6}
};
  • 2차원 char 배열은 여러 개의 문자열을 저장할 수 있다.
char names[3][10] = {
    "Alice",
    "Bob",
    "Charlie"
};
  • 문자열을 2차원 배열에 초기화하여 여러 문자열을 관리할 수 있다.
for (int i = 0; i < 3; i++) {
    printf("%s\n", names[i]);
}
  • 3차원 배열은 3개의 차원을 가지며, 주로 여러 개의 2차원 배열을 관리할 때 사용된다.
int cube[2][3][4];  // 2층, 3행, 4열의 3차원 배열

 

포인터 배열

  • 포인터 배열은 여러 개의 포인터를 저장하는 배열로, 각 포인터는 다른 변수나 배열을 가리킬 수 있다.
int a = 10, b = 20, c = 30;
int *arr[3] = {&a, &b, &c};

for (int i = 0; i < 3; i++) {
    printf("arr[%d]가 가리키는 값: %d\n", i, *arr[i]);
}
  • 포인터 배열을 사용하면 2차원 배열처럼 데이터를 관리할 수 있다.
char *messages[3] = {
    "Hello",
    "World",
    "C Language"
};

for (int i = 0; i < 3; i++) {
    printf("%s\n", messages[i]);
}

3. 응용 포인터

 

이중 포인터와 배열 포인터

  • 이중 포인터는 포인터를 가리키는 포인터로, 포인터의 주소를 저장한다.
  • 이중 포인터는 다차원 배열이나 동적 메모리 할당 시 유용하게 사용된다.
int num = 10;
int *p = &num;
int **pp = &p;
printf("num: %d, *p: %d, **pp: %d\n", num, *p, **pp);  // 출력: 10, 10, 10
  • 이중 포인터를 사용하면 함수 내에서 포인터 변수 자체의 값을 변경할 수 있다.
void changePointer(int **pp) {
    static int newValue = 50;
    *pp = &newValue;
}

int main() {
    int value = 10;
    int *p = &value;
    changePointer(&p);
    printf("p가 가리키는 값: %d\n", *p);  // 출력: 50
    return 0;
}
  • 이중 포인터를 사용하여 포인터 배열을 함수의 매개변수로 전달할 수 있다.
void printMessages(char **messages, int size) {
    for (int i = 0; i < size; i++) {
        printf("%s\n", messages[i]);
    }
}

int main() {
    char *messages[] = {"Hello", "World", "C Programming"};
    printMessages(messages, 3);
    return 0;
}
  • 배열의 요소와 배열 자체는 다른 주소를 가진다.
  • 배열의 이름은 배열의 첫 번째 요소의 주소이지만, 배열의 주소와는 다르다.
int arr[5] = {1, 2, 3, 4, 5};
printf("arr: %p, &arr: %p\n", (void *)arr, (void *)&arr);  // 출력: 서로 다른 주소
  • 2차원 배열을 가리키는 포인터는 배열 포인터를 사용하여 선언할 수 있다.
int matrix[2][3] = {{1, 2, 3}, {4, 5, 6}};
int (*pMatrix)[3] = matrix;
for (int i = 0; i < 2; i++) {
    for (int j = 0; j < 3; j++) {
        printf("%d ", pMatrix[i][j]);
    }
    printf("\n");
}
  • 2차원 배열에서 요소를 참조할 때는 행과 열의 인덱스를 사용하며, 배열 포인터를 통해 쉽게 접근할 수 있다.

함수 포인터와 void 포인터

  • 함수 포인터는 함수의 주소를 저장하는 포인터로, 함수 호출을 동적으로 제어할 수 있다.
  • 함수 포인터를 사용하면 코드의 유연성을 높일 수 있다.
int add(int a, int b) {
    return a + b;
}

int main() {
    int (*funcPtr)(int, int) = add;
    printf("두 수의 합: %d\n", funcPtr(5, 7));  // 출력: 12
    return 0;
}
  • 함수 포인터는 콜백 함수나 동적 함수 호출 등에 사용된다.
  • 이를 통해 함수의 호출 방식을 유연하게 구성할 수 있다.
  • void 포인터는 어떤 자료형의 포인터든 가리킬 수 있는 범용 포인터이다.
  • 하지만 역참조를 위해서는 명시적으로 형변환이 필요하다.
void printValue(void *ptr, char type) {
    switch (type) {
        case 'i':
            printf("정수 값: %d\n", *(int *)ptr);
            break;
        case 'f':
            printf("실수 값: %f\n", *(float *)ptr);
            break;
    }
}

int main() {
    int num = 42;
    float fnum = 3.14;
    printValue(&num, 'i');  // 출력: 정수 값: 42
    printValue(&fnum, 'f');  // 출력: 실수 값: 3.140000
    return 0;
}

C 언어 기본 변수, 다차원 배열, 응용 포인터 소개 이미지