C Part5. 배열과 문자열, 2차원 배열, 포인터, 운영체제의 메모리 관리 방식(직접주소, 간접주소 지정방식), 포인터와 const 키워드, void *형 포인터

12. 배열과 문자열

배열

  • 배열은 자료형이 같은 변수들을 그룹으로 묶어서 관리할 때 사용한다.
  • 색인(인덱스)는 0부터 시작한다.
void main(){
    short student[20];   //short 타입 변수 20개 만들어라

    student[1] = 10;  // 배열의 두번째 요소에 10 대입
}

배열을 사용할 때 주의점

[]안에 반드시 상수를 적어야 한다.

short student[20]; //20은 무조건 상수!

int CNT = 30;
int arr[CNT]; //오류!!! 변수 안된다.

배열 초기화

배열의 각 요소에 일정한 값을 대입하여 초기화해서 사용해야 한다.

쉼표를 사용한 배열 초기화

void main(){
    short student[20] = {0, };

    short data[5] = {3, };  // 결과 -> 3, 0, 0, 0, 0 처럼 된다
}

배열 크기를 생략하여 사용할 수 있음

short data[] = {1, 2, 1, 2, 1};
void main(){
    char data[5] = {1, 2, 3, 4, 5};
    int result = 0, i;

    for(i = 0; i < 5; i++){
        result = result + data[i];
    }
    printf("data 배열의 각 요소의 합은 %d입니다.", result);
}

// 결과 -> data 배열의 각 요소의 합은 15입니다.

문자열

  • C언어에서 문자를 저장하는 데 가장 적합한 자료형은 char형
  • 문자의 끝에 NULL(널) 문자 0을 추가로 입력해서 ‘이 배열에 저장된 정보는 문자열이다’ 라고 컴파일러에게 알려줘야 한다. (문자의 끝에 0을 넣어주는것은 컴파일러가 왠만하면 알아서 해줌. 굳이 내가 안써도 된다)
  • NULL 문자는 0이라고 적어도 되고 ‘\0’이라고 적어도 된다. (왠만하면 역슬래시 써주는게 좋음)
  • 배열의 크기와 문자열의 길이는 다르다 ex) HELLO = 배열의 크기:6, 문자열의 길이:5
char data[6] = {'h', 'a', 'p', 'p', 'y', 0};  //문자개수는 5개이고 끝에 0을 덧붙여야 하므로 배열의 크기는 6
char data[6] = "happy";  //이렇게 쓰면 문자열의 끝에 NULL 문자 0이 자동으로 포함된다

  • strlen(문자열이 저장된 변수 이름)
  • strcpy(복사해서 저장할 변수 이름, 복사할 기존 변수 이름)
  • strcat(기존 문자열이 저장된 변수 이름, 새로 덧붙일 문자열)

예제) 문자열의 길이 구하기

#include <string.h> //문자열 표준 함수를 사용하기 위해 추가함

void main(){
    int data_length;
    char data[10] = {'h','a','p','p','y',0};

    data_length = strlen(data);
    printf("data length = %d", data_length);
}
//결과 -> data length = 5

예제) 두 개의 문자열 합치기

#include <string.h>   //문자열 표준 함수를 사용하기 위해 추가함

void main(){
    char data[10] = "abc";
    char result[16];

    strcpy(result, data);
    strcat(result, "def");

    printf("%s + \"def\" = %s", data, result);
}
//결과 -> abc + "def" = abcdef

2차원 배열

  • 대괄호 []를 두 번 사용해서 선언하는 배열이 2차원 배열이다.
  • []연산자는 동일한 우선순위를 가질 때 왼쪽에서 오른쪽으로 연산을 수행한다.
  • 왼쪽에 있는 []연산자를 먼저 처리한다

2차원배열 형식 들어가면 오류가 남…………..

예제) 1차원 배열의 바둑판

void main(){
    char data[12] = {0,0,2,0,1,1,0,0,2,1,0,2};
    int i, x, y;

    for(i = 0; i < 12; i++){
        x = i % 4 + 1; //열번호 구함
        y = i / 4 + 1; //행번호 구함
        printf("%d행 %d열에", y, x);

        if(data[i] == 1){
            printf("검은 돌이 놓여있습니다\n");
        }else if(data[i] == 2){
            printf("흰 돌이 놓여있습니다\n");
        }else{
            printf("돌이 없습니다.\n");
        }
    }
}
//결과 -> 1행 1열에돌이 없습니다.
//        1행 2열에돌이 없습니다.
//        ...
//        3행 3열에돌이 없습니다.
//        3행 4열에흰 돌이 놓여있습니다

예제) 2차원 배열의 바둑판

void main(){
    char data[3][4] = { {0,0,2,0,}, {1,1,0,0,}, {2,1,0,2} };
    int x, y;

    for(y = 0; y < 3; y++){
        for(x = 0; x < 4; x++){
            printf("%d행 %d열에 ", y+1, x+1);

            if(data[y][x] == 1){
                printf("검은 돌이 놓여있습니다.\n");
            }else if(data[y][x] == 2){
                printf("흰 돌이 놓여있습니다.\n");
            }else{
                printf("돌이 없습니다.\n");
            }
        }
    }
}
//결과 -> 1행 1열에돌이 없습니다.
//        1행 2열에돌이 없습니다.
//        ...
//        3행 3열에돌이 없습니다.
//        3행 4열에흰 돌이 놓여있습니다

13. 포인터

운영체제의 메모리 관리 방식

운영체제와 프로그래밍
  • C언어 소스코드에서 사용한 변수들은 컴파일 작업 후 기계어로 번경되면 모두 메모리 주소로 바뀌어서 적용된다.
  • 결국 기계어에서는 변수 이름보다 변수가 위치한 메모리의 주소가 중요하다.

32비트 운영체제와 64비트 운영체제
  • 실제 개발 현장에는 32비트로 개발하는 경우가 많다. 32비트 방식으로 개발해도 64비트 운영체제에서 모두 동작하기 때문.
  • 32비트 운영체제는 우리가 메모리라고 부르는 RAM을 4GB(기가바이트, 230)밖에 사용하지 못하지만 64비트 운영체제에서는 16EB(엑사바이트, 260)까지 사용할 수 있다.
  • 따라서 자신의 시스템이 RAM을 4GB이상 사용한다면 64비트 운영체제를 설치해야 메모리를 100% 다 사용 할 수 있다.
  • 64비트의 단점은 기본적으로 메모리 사용량이 많으며 낮은 사양의 컴퓨터에 64비트 운영체제를 설치하는 것은 오히려 손해이다.

메모리 주소 지정 방식
  • 메모리를 사용하려면 반드시 사용할 주소를 지정해야하고, 메모리가 1바이트 단위로만 사용되는 것은 아니기 때문에 프로그래머가 메모리를 사용할 때 한 번에 읽거나 저장할 크기를 명시해야 한다.
  • 메모리 주소번지까지 컨트롤 하는 경우는 거의 없다.


*컴퓨터는 오직 2진수(0, 1)를 사용한다 *C는 보통 16진수를 활용하며 컴파일러가 알아서 2진수로 변환해준다.


직접 주소 지정 방식
  • C언어의 '변수' 문법과 같다.
  • 번역기의 도움을 받아서 내부적으로 변수가 주소로 변환되어 결과적으로는 직접 주소 지정 방식을 사용하게 되는 것이다.
  • 직접 주소 지정 방식의 한계 : 각 함수의 지역 변수는 해당 함수 안에서만 사용 가능

간접 주소 지정 방식
  • 32비트 운영체제에서는 크기를 4바이트로 고정해야 한다.(64비트는 8바이트)

포인터
  • c언어에서 직접 주소 지정 방식변수 문법이라고 했다.
  • 간접 주소 지정 방식은 값을 저장할 '주소'를 메모리에 저장하는 것이다.

  • 포인터 문법을 사용해 선언한 포인터 변수메모리 주소**만**을 저장하기 위해 탄생한 특별한 변수이다.
  • 포인터 변수는 크기가 무조건 4바이트로 정해져있기 때문에 변수의 크기를 적을 필요가 없다.
short birthday;
short *ptr; //포인터 변수 선언
ptr = &birthday; //birthday 변수의 주소를 ptr 변수에 대입
*ptr = 1042 //ptr에 저장된 주소에 가서 값 1042를 대입함. 즉 birthday = 1042

short *ptr = &birthday; //포인터 변수를 선언!
*ptr = 1042; //ptr 포인터가 가리키는 대상에 가서 1042 값을 대입하겠다는 의미. 정의!
//위와 아래는 같지 않다.

예제) call by value. 일반변수는 값 변동x

void Test(short data){
    short soft = 0;
    soft = data;  //soft = 5
    tips = 3;  //오류 발생
}
void main(){
    short tips = 5;
    Test(tips);
}
//data = tips;


예제) call by reference. 포인터 사용시 원본 값 변경된다.

void Test(short *ptr){
    short soft = 0;
    soft = *ptr;   //soft = tips;
    *ptr = 3;   //tips = 3;
}
void main(){
    short tips = 5;
    Test(&tips);
}
//ptr = &tips;

예제) 간접 주소 지정 방식(포인터)으로 변수 값 교환하기

void Swap(int *pa, int *pb){
    int temp = *pa; //참조
    *pa = *pb; //복사
    *pb = temp; //참조
}
void main(){
    int start = 96, end = 5;

    if(start > end){
        Swap(&start, &end);
    }

    printf("start : %d, end : %d", start, end);
}
// 결과 -> start : 5, end : 96

인자값이 2개라서 return값은 하나만 되기 때문에 포인터 사용한다. C에서만 return값을 하나만 사용할 수 있다.


포인터와 const 키워드

주소 변경 실수를 방지하기 위해 const 키워드를 사용한다.

  • int * const p; : 주소를 변경하면 오류 발생
  • const int *p; : 대상의 값 변경시 오류 발생
  • const int * const p; : 둘 다 잠금

포인터 변수의 주소 연산

배열에서 사용시 좋다!!

포인터 변수에 +1을 하면 자신이 가리키는 대상의 크기만큼 증가한다.

short data = 0;
short *p = &data;
p = p+1; //포인터 변수에 저장된 주소 값을 1만큼 증가시킴
char *p1 = (char *)100; //p1에 100번지를 저장함
short *p2 = (short *)100;
int *p3 = (int *)100;
double *p3 = (double *)100;
p1++; //1바이트이기 때문에 p1에 저장된 주소값이 101
p2++; //2바이트라서 102
p3++; //4바이트라서 104
p4++; //8바이트라서 108

포인터가 가리키는 대상의 크기

변수 p에 일반 변수 data의 주소 값을 저장하고 포인터 변수 p를 이용하여 data 변수의 값을 변경하는 경우에는 두 변수의 자료형을 같게 지정하는것이 일반적이다.

int data = 0;
short *p = (short *)&data;
//앞에 두칸만 갖다 사용

예제) int형 변수에 저장된 값을 1바이트 단위로 출력

void main(){
    int data = 0x12345678, i;
    char *p = (char *)&data;

    for(i = 0; i < 4; i++){
        printf("%x, ", *p);
        p++;
    }
}
// 결과 -> 78, 56, 34, 12,

위 예제는 p가 가지고 있는 주소 값을 옮기는 방식으로 작업했는데, p의 주소 값을 변경하지 않고 data 변수 값을 1바이트씩 출력하고 싶다면 반복문 코드만 다음과 같이 변경하면 된다.

for(i = 0; i < 4; i++){
    printf("%x, ", *(p + i) );
}

void *형 포인터

  • 주소 값을 저장할 수는 있지만 해당 주소에서 값을 읽거나 저장할 때 사용하는 크기는 정해져 있지 않다.
  • 즉 사용할 메모리의 시작 주소만 알고 끝 주소를 모를 때 사용하는 포인터 형식이다.
  • void * 는 주소를 사용할 때 반드시 ‘사용할 크기’를 표기해야 한다.
int data = 0;
void *p = &data; //data의 시작 주소를 저장함
*p = 5 //오류 발생. 대상 메모리의 크기가 지정되지 않음.

int data = 0;
void *p = &data;
*(int *)p = 5; //형 변환 문법을 사용하여 대상의 크기를 4바이트로 지정하므로 data 변수에 5가 저장됨

예제) void *를 사용항 대상 메모리의 크기 조절하기

int GetData(void *p_data, char type){
    int result = 0;

    if(type == 1){
        result = *(char *)p_data; 
    }else if(type == 2){
        result = *(short *)p_data;
    }else if(type == 4){
        result = *(int *)p_data;
    }
    return result;
}
void main(){
    int data = 0x12345678;
    printf("%x", GetData(&data, 2));
}
//결과 -> 5678

Comments