[HARMAN] 세미콘 아카데미/Embedded

[Harman 세미콘 아카데미] Day_87(Embedded)

uiop1716 2025. 6. 17. 09:08

포인터 : 특정 값이 저장된 메모리의 주소에 접근하기 위해 사용, (주소를 저장하는 메모리 공간)

      ㄴ 식 : int *pa -> 주소를 구한 변수의 형태를 포인터 기호(*)를 통해 지정

 

 

위의 코드를 실행하여 조사식을 확인해 보면, a라는 변수가 있는 메모리의 주소가 pa에 0x5ffec4라는 값으로 저장이 되고, 이를 포인터로하여 해당 위치에 10이라는 값을 저장하게 됩니다. 그래서, 0x5ffec4라는 메모리 주소에 10이라는 값이 저장되게 됩니다.

포인터 코드

 

Little Endian : CPU가 읽는 방식 -> 1234를 "일이삼사"라고 읽고, 4 -> 3 -> 2 -> 1 순으로 저장 (LSB가 낮은 메모리에 저장)

Big Endian : 사람이 읽는 방식  -> 1234를 "천이백삼십사" 라고 읽고, 1 -> 2 -> 3 -> 4 순으로 저장 (MSB가 낮은 메모리에 저장)

 

 

 

 

주소가 기본적으로 8byte이기 때문에, 주소 값을 저장하는 포인터의 크기를 확인하면 8이 나오게 됩니다 (만약 주소가 4bye라면 포인터의 크기는 4byte). int의 자료형을 갖는 a의 변수 크기를 확인하면 4byte임을 확인할 수 있고, char의 자료형을 갖는 b의 변수 크키는 1byte를 담고 있음을 확인할 수 있습니다.

--> 포인터의 크기는 주소의 크기와 동일하다는 것을 알 수 있습니다.

 

※주의 사항 : 포인터 사용시, 포인터의 변수 Type과 입력값의 Type을 맞춰줘야 합니다. -> 형 변환 사용 가능

        ㄴ double a = 3.5; double *pd = &a; int *pi; , pi = (int *)pd ( -> int형으로 변환);

 

 

 

 

포인터 활용 Swap 함수 예제 :

Swap함수의 매개 변수는 포인터 즉, 주소값이 들어가야 하므로, 10과 20의 값을 저장하고 있는 주소값을 받게 됩니다. temp라는 변수를 활용하여, a와 b가 가리키는 주소값을 변경합니다. 또한, temp는 *주소의값 을 가리키므로 결과적으로 a와 b의 값이 변경됩니다. 

#include <stdio.h>

void swap(int *pa, int *pb);

int main(void)
{
    int a = 10, b = 20;

    swap(&a, &b);
    printf("a: %d, b: %d\n", a, b);  
}

void swap(int *pa, int *pb)
{
    int temp;

    temp = *pa;
    *pa = *pb;
    *pb = temp;
}

 

 

결과 :

a = 20, b = 10의 값을 갖게 됨

 

 

 

 

Swap이 안되는 예제 :

메모리를 참고하여 확인하면, x와 y 그리고 temp에 대한 새로운 영역이 생성이 됩니다. 하지만, a와 b에 대한 주소를 가지지 않기 때문에 a와 b의 변수에 접근을 할 수 없어 결론적으로 값이 swap되지 않습니다. (독립적인 swap 함수 실행)

--> Original Data( a , b )를 접근하기 위해 포인터를 사용

#include <stdio.h>

void swap(int x, int y);

int main(void)
{
    int a = 10, b = 20;

    swap(a, b);
    printf("a: %d, b: %d\n", a, b);
   
    return 0;
}

void swap(int x, int y)
{
    int temp;

    temp = x;
    x = y;
    y = temp;
}

Memory 영역

 

 

결과 : a와 b의 값 간의 swap이 일어나지 않음

 

 

 

 

 

배열과 포인터 :

  ※ 배열의 이름은  ->  배열의 첫번째 주소 + 대표 주소

ary = 배열의 대표 주소를 나타내므로, 0 1 2의 index를 지니고 *를 통해 값을 저장하게 됩니다. 그리하여 2번째 값은 scanf를 통해 값을 입력 받고, 나머지 0 1번지에는 10과 20의 값을 저장합니다.

#include <stdio.h>

void swap(int x, int y);

int main(void)
{
    int ary[3];
    int i;
    // *(ary + 0) == ary[0] , *(ary + 1) == ary[1]
    *(ary + 0) = 10; // *(배열의 이름 = 주소 + 0) -> 주소 + 0의 값
    *(ary + 1) = *(ary + 0) + 10; //

    printf("세 번째 배열 요소에 키보드 입력 :");
    scanf("%d", ary+2);

    for(int i = 0; i < 3; i++){
        printf("%5d", *(ary + i));
    }
    return 0;
}

 

결과 : 30의 값은 입력하여 10, 20, 30의 값을 배열에 저장한 모습

 

 

 

※ 포인터도 배열로 사용할 수 있다

배열명과 포인터의 차이 :

int ary[3] -> 4byte *3, 총 12byte의 사이즈 

int *pa = ary; -> 4byte 1개, 총 4byte의 사이즈

sizeof(ary[3]) -> 12 

sizeof(pa) -> 4

 

 

ASCII 코드 : 128개의 문장에 대해 서로 다른 값을 정해놓은 약속

알파벳의 대문자와 소문자의 ASCII상 차이는 32입니다.

ASCII Code 표

 

 

문자열 : 문자를 담고 있는 배열, 문자열은 주소를 반환합니다. 즉, "apple"이라는 문자열이 메모리 내 저장된 주소를 반환하여 접근할 수 있도록 합니다.

---> 문자열은 주소(배열의 첫번째 주소)다 !

#include <stdio.h>

int main(void)
{
    printf("apple이 저장된 시작 주소 값 : %p\n", "apple");  //%p -> 주소를 표현
    printf("두 번째 문자의 주소 값 : %p\n", "apple" + 1);
    printf("첫 번째 문자 : %c\n", *"apple");
    printf("두 번째 문자 : %c\n", *("apple"+1));
    printf("배열로 표현한 세 번째 문자 : %c\n", "apple"[2]);

    return 0;
}

 

 

결과 :

문자열 자체가 주소값이므로 1번째는 "apple"이 저장된 주소를, 2번째 printf를 통해 주소값 + 1의 값을 출력합니다.

3번째 printf는 *(첫번째)의 형태와 동일하므로, "apple"문자열이 저장된 배열의 첫번째인 "a"를 출력합니다.

4번째 printf는 배열의 두번째 값을 지정하므로 "p"라는 값을 출력합니다.

5번째 printf는 "apple" 문자열을 담고 있는 배열의 3번째 값이므로 "p"를 출력하게 됩니다.

 

 

문자열을 대입하는 함수 strcpy : 복사하고자 하는 내용을 새로운 배열에 저장

          ㄴ strcpy (str1, str2) -> str2의 내용을 str1에 Copy함

 

원하는 개수의 문자만을 복사하는 strncpy :

           ㄴ strncpy(str, "apple-pie", 5) -> "apple" 만 복사

 

문자열 길이 측정 strlen : Null까지의 길이를 측정

           ㄴ strlen(str1) -> if str1 = "apple"  --> 5

 

문자열 비교 strcmp, strncmp : ASCII 코드 값을 기준으로 문자열의 값을 하나씩 비교함

           ㄴ strcmp(str1, str2)  -> 같으면 0 반환, 비교하여 참이면 1

 

 

변수 : 

    1. 지역 변수 : 해당 scope 내부에서만 사용되는 변수

    2. 전역 변수 : 코드 전역에서 사영되는 변수로 메모리의 .bss 영역에 할당됨 

    ※ 같은 이름일 때 우선순위 : 지역 변수 > 전역 변수

Memory 내 변수의 영역

 

3. 정적 지역 변수 : 해당 scope 내부에서만 사용되는 변수, 메모리 공간을 반환하지 않고 유지, .data 영역에 할당

           ㄴ ex) static int a;

 

#include <stdio.h>

void auto_func(void);
void static_func(void);

int main(void)
{
    int i;
    printf("일반 지역 변수를 사용한 함수\n");
    for(i = 0; i < 3; i++) {
        auto_func();
    }

    printf("정적 지역 변수를 사용한 함수\n");
    for(i = 0; i < 3; i++) {
        static_func();
    }

    return 0;
}

void auto_func(void)
{
    int a = 0;

    a++;
    printf("%d\n",a);
}

void static_func(void)
{
    static int a;

    a++;
    printf("%d\n", a);
}

 

 

결과 :

일반 지역 변수를 사용하면, 메모리에 할당되고 삭제되고를 반복하며 a는 지속적으로 0이되고, printf 전 a++을 통해 계속 1을 출력합니다. 정적 지역 변수는 메모리를 유지하기 때문에 계속 a++ 되어 출력되기 때문에 2, 3, 4의 값이 출력됩니다.

 

 

다차원 배열 :

     ㄴ 형태 : int score[3][4]  -> 4개(요소) 짜리가  3묶음으로 존재 -> 묶음을 표현하기 위해 주소 사용 (%s)

     ㄴ { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12} }와 같은 문장으로 존재

 

초기화 방법 :

    1.  일부 초깃값 생략 가능

    2.  행의 수 생략 가능

    3.  1차원 배열의 초기화 방식으로 초기화

    4.  초기화시 남는 저장공간은 0으로 자동 초기화

    5.  행의 수가 생략되고 초깃값이 적은 경우

 

 

포인터 배열 선언과 사용

         ㄴ ex) char *pary[5] : 5개의 요소로 이뤄진 char type의 pary = *pary x 5 = 8byte * 5 = 40byte

         ㄴ 주소를 담을 수 있는 배열

 

 

 

이중 포인터 : 주소를 담고 있는 포인터의 주소를 다시 저장

       ㄴ 형태 : int **ppi; 

이중 포인터 관련 그림

 

 

이중 포인터를 사용하여, Data를 담고 있는 주소를 swap하여 Memory에 저장되어 있는 문자열을 변경하는 예제

 

 

 

 

ary = ary 배열의 첫번째 주소 (1개의 index), ary는 전체 배열의 주소 -> 4byte * 5 만큼 추가된 모습

 

 

함수 포인터 : 함수의 주소를 담을 수 있는 변수  (객체 지향 언어의 다형성을 이룸)

        ㄴ 형태 : int (*fp) (int, int); 

        ㄴ 해당 함수를 대신해 주는 역할

#include <stdio.h>

int sum(int a, int b);
int sub(int a, int b);

int main(void)
{
    int (*fp)(int, int);  //함수 포인터 (함수의 주소 저장 변수) 선언
    int res;

    fp = sum;  //sum : 함수명 = 함수의 주소, fp : 함수의 주소를 받아 함수의 역할
    res = fp(20,10);
    printf("result = %d\n", res);

    fp = sub;  //sub : 함수명 = 함수의 주소, fp : 함수의 주소를 받아 함수의 역할
    res = fp(20,10);
    printf("result = %d\n", res);

    return 0;
}

int sum(int a, int b)
{
    return (a + b);
}

int sub(int a, int b)
{
    return (a - b);
}

             -- >  다형성 : fp라는 동일한 변수명을 가지고 "sum", "sub"의 함수의 주소를 할당받아 명령어 수행을 하였기 때문 

 

 

결과 : 

int (*fp)(int, int)로 fp라는 함수 포인터가 선언이 되고, 'sum' 함수의 이름 즉, 주소를 할당받게 됩니다. 그러면, 함수 포인터는 함수의 역할을 대신하기 때문에 10과 20의 값을 입력하면 덧셈 결과인 30을 출력하고 res에 저장하게 됩니다.(sub도 동일)

 

 

 

Callback 함수 :  함수 포인터를 매개변수로 활용

          ㄴ ex) void func int(*fp) (int, int);

 

void 포인터 : 자료형이 없는 포인터, 그러므로 모든 포인터를 다 받게됨 + Casting(자료형 선언) 꼭하기!

          ㄴ ex) void *vp;

#include <stdio.h>

int main(void)
{
    int a = 10;
    double b = 3.5;
    void *vp;

    vp = &a;
    printf("a : %d\n", *(int *)vp); //casting (자료형 선언) 꼭 하기

    vp = &b;
    printf("b : %.1f\n", *(double *)vp); //casting 꼭 하기

    return 0;
}

 

 

 

메모리 동적 할당 (malloc) : heap영역에 임시의 동적 메모리 영역을 할당함

         ㄴ 형태 : (type *)malloc(sizeof(type)) -> type만큼의 byte를 할당하고 첫번째 주소를 return

         ㄴ ex) (int *) malloc(sizeof(int) * 5)  -> 4byte * 5   ==> 20byte를 갖는 메모리 영역 할당

         ㄴ free 함수를 만나기 전까지 메모리 영역에 저장하게됨 -> 안하면 "메모리 누수(leak)" 발생

메모리 내 malloc 할당

 

calloc : 할당한 공간을 0으로 초기화

    - 형태 : void *calloc(unsigned int, unsigned int);

realloc : 할당한 공간의 크기를 늘이거나 줄임

    - 형태 : void *realloc(void *, unsigned int);

 

 

 

사용자 정의 자료형 : 

      1. 구조체 : 여러 자료형의 변수들을 하나로 묶어 하나의 단위로 정의한 자료형 (관리하기 편함)

#include <stdio.h>

struct student
{
    int num;
    double grade;
};

int main(void)
{
    struct student s1

    s1.num = 2;  //구조체의 순서대로 메모리 할당이 선서대로 진행 num -> grade 순
    s1.grade = 9.7;
    printf("학번 : %d\n", s1.num);
    printf("학점 : %.1lf", s1.grade);

    return 0;
}

 

 

결과 :

s1이라는 명칭의 구조체의 구성 요소인 num과 grade를 s1.num, s1.grade의 형태로 값 변수에 접근합니다. 이를 출력하면 아래의 결과와 같이 나옵니다.

 

구조체 변수의 크기 : 실행 효율을 위해 패딩 바이트를 넣어 바이트 정렬

           ㄴ ex) struct student { int num; double grade; } 시, 8byte(double) + 8byte(4bye + padding) = 16byte의 크기를 가짐 

 

---> 구조체의 순서만 바꾼다면 최적화된 메모리 할당으로 실행 효율을 높일 수 있습니다.

 

구조체 포인터와 -> 연산자 : 해당 주소의 멤버로 접근 가능 

#include <stdio.h>

struct score
{
    int kor;
    int eng;
    int math;
};

int main(void)
{
    struct score yuni = {10, 20, 30};
    struct score *ps = &yuni;

    printf("국어 : %d\n", (*ps).kor);
    printf("영어 : %d\n", ps -> eng);
    printf("수학 : %d\n", ps -> math);

    return 0;
}

 

결과 : 

*ps = &yuni 를 통해 yuni 구조체의 값들에 접근할 수 있음. 이를  ps -> 변수 를 통해 불러옴 

 

 

공용체 : 공용체의 멤버들에 따라 메모리 byte가 변화함. int 멤버 시 4byte, double 멤버 시 8byte

     ㄴ 형태 : union student { int num; double grade; }; 

 

열거형 : 관련된 상수들을 의미있는 이름으로 묶어놓음

     ㄴ 형태 : enum season {SPRING, SUMMER, FALL, WINTER};

 

전처리 : Compile 기준으로 처리

 

분할 컴파일 : 파일을 분할하여 따로따로 compile하고 link를 거쳐 실행 파일을 만듬