공부/C++

구조체, 구조체 초기화, 구조체와 메모리, 메모리 패딩, 구조체 포인터, 구조체와 함수, 값 전달, 주소 전달, 구조체 맴버함수

월러비 2025. 6. 16. 17:55

구조체

// C에서는 struct 키워드를 매번 써야 함
struct Point {
    int x, y;
};
struct Point p1;  // C 스타일

// typedef를 사용하면 struct 키워드 생략 가능
typedef struct {
    int x, y;
} Point;
Point p1 = {10, 20};  // struct 키워드 불필요
  • 하나의 주체가 여러 설정을 가지고 있는 경우 등
    • Vector2f 데이터형도 구조체다.
  • 사용자 정의 열거형과 같은 ‘사용자 정의 데이터형’이다.
struct { float x, y; } pos;  // 익명 구조체

pos.x = 1.5;
pos.y = 2.2;
printf("좌표 : (%f, %f)\\\\n", pos.x, pos.y);
  • 위와 같은 형태는 예시용이고 되도록 사용하지 않는다.
    • 구조체의 이름없이 사용하는 형태이기 때문이다.
      • 익명 구조체 라고 한다.
    • 일회용이다.

구조체 구성 요소

  • struct : 구조체 선언 키워드
  • { float x, y; } : 구조체 멤버 정의
    • 멤버: 구조체를 구성하는 개별 변수들
    • 여러 타입의 변수 포함 가능
  • pos : 구조체 변수 이름
struct ProductInfo {    // 구조체 타입 이름
    int num;           // 멤버 1: 제품 번호
    char name[100];    // 멤버 2: 제품 이름
    int cost;          // 멤버 3: 제품 가격
};

int main() {
    ProductInfo myProduct = { 4797283, "제주 한라봉", 19900 };

    printf("제품 번호 : %d\\\\n", myProduct.num);
  • 구조체 메모리 : 안의 요소의 데이터형의 바이트 크기 합만큼의 메모리가 구조체 객체를 생성할때마다 생성된다.
    • 위의 메모리 크기 : 4 + 100 + 4 : 108바이트
    • 메모리 크기를 공유하는것이 아니라 개별적인 방이 있다고 생각해야한다.
      • 해당 데이터형의 메모리에만 읽고 써지는 것이다.
      • .num으로 사용하게된다.

구조체 초기화 방법

// 초기화와 함께 선언
ProductInfo product1 = { 1001, "키보드", 50000 };
ProductInfo product2 = { 1002, "마우스", 30000 };

// 선언 후 개별 대입
ProductInfo product3;
product3.num = 1003;
strcpy(product3.name, "모니터");  // 문자열은 strcpy 사용
product3.cost = 200000;

// 부분 초기화
ProductInfo product4 = { 1004 };  // name은 "", cost는 0

//전체 복사
ProductInfo product5 = product3;
  • 중괄호 초기화: { 값1, 값2, 값3 }
  • 멤버 순서대로 값 지정
  • 일부만 초기화 시 나머지는 0 또는 빈 값
  • 동일한 구조체의 객체에 다른 객체의 전체 요소의 값을 복사할 수 있다.

구조체 복사

  • 구조체 전체를 한 번에 복사 가능
  • product1 = product2; // 모든 멤버가 복사됨

구조체와 메모리

struct ProductInfo {
    int num;         // 4바이트
    char name[100];  // 100바이트
    int cost;        // 4바이트
};

int main() {
    ProductInfo myProduct = { 4797283, "제주 한라봉", 19900 };

    printf("%d\\n", sizeof(myProduct));      // 전체 크기: 108
    printf("%d\\n", &myProduct);             // 구조체 시작 주소
    printf("%d\\n", &myProduct.num);         // num의 주소 (= 구조체 시작 주소)
    printf("%d\\n", myProduct.name);         // name 배열의 주소
    printf("%d\\n", &myProduct.cost);        // cost의 주소
    
    1000        num            4바이트
		1004        name[0~99]     100바이트
		1104        cost           4바이트
		-----------------------------
		총 크기: 108바이트
}

메모리 구조 분석

  • 구조체 멤버는 선언 순서대로 메모리에 배치된다.
  • 첫 번째 멤버 주소 = 구조체 시작 주소
  • 각 멤버는 연속된 메모리 공간 차지

메모리 패딩

struct ProductInfo {
    int num;         // 4바이트
    char name[101];  // 101바이트로 변경
    int cost;        // 4바이트
    
    1000        num            4바이트
		1004        name[0~100]    101바이트
		1105        (패딩)         3바이트    <- 자동 추가
		1108        cost           4바이트
		-----------------------------
		총 크기: 112바이트
};

  • CPU 성능 최적화를 위한 메모리 정렬
  • 컴파일러가 자동으로 빈 공간 추가
  • 데이터를 4바이트(또는 8바이트) 경계에 맞춤
    • 컴파일러의 설정에 따라 바뀐다.
  • 패딩이 발생하는 이유
  • CPU는 메모리를 word 단위로 읽음 (32비트: 4바이트)
  • 정렬되지 않은 데이터는 여러 번 읽어야 함
  • 속도 향상을 위해 메모리 낭비 감수

구조체 변수 가리키기 (포인터)

구조체 포인터의 필요성

  • 함수에 구조체 전달 시 효율성 향상
  • 동적 메모리 할당 시 필수
  • 연결 리스트, 트리 등 자료구조 구현
  • 원본 구조체 수정이 필요한 경우
struct ProductInfo {
    int num;
    char name[100];
    int cost;
};

int main() {
    ProductInfo myProduct = { 4797283, "제주 한라봉", 19900 };
    ProductInfo* ptr_product = &myProduct;  // 구조체 포인터
    
    //*ptr_product; //ProcudtInfo 형이 된다. 

    // (*포인터).멤버 형식으로 접근
    printf("제품 번호 : %d\\n", (*ptr_product).num);
    printf("제품 이름 : %s\\n", (*ptr_product).name);
    printf("제품 가격 : %d원\\n", (*ptr_product).cost);
    
    //위와 같은 결과
    //화살표 연산자로 간단하게 접근
    printf("제품 번호 : %d\\n", ptr_product->num);
    printf("제품 이름 : %s\\n", ptr_product->name);
    printf("제품 가격 : %d원\\n", ptr_product->cost);
}
  • → : 포인터 멤버 접근 연산자
    • 좌변 : 멤버를 갖고있는 포인터 형 / 우변 : 해당 포인터의 멤버 변수 또는 함수
  • ptr_product : 포인터가 가리키는 구조체
  • (*ptr_product).num : 구조체의 num 멤버
  • 괄호 필수: . 연산자가 보다 우선순위 높음

화살표 연산자의 의미

  • ptr->member = (*ptr).member
  • 포인터를 통한 멤버 접근 전용 연산자
  • 코드 가독성 향상

접근 연산자 정리

  • 일반 구조체 변수: . (점)
  • 구조체 포인터: > (화살표)

구조체와 함수

struct ProductInfo {
    int num;
    char name[100];
    int cost;
};

void productSwap(ProductInfo* a, ProductInfo* b) {
    ProductInfo tmp = *a;  // a가 가리키는 구조체를 tmp에 복사
    *a = *b;              // b의 내용을 a가 가리키는 곳에 복사
    *b = tmp;             // tmp의 내용을 b가 가리키는 곳에 복사
}

int main() {
    ProductInfo myProduct = { 4797283, "제주 한라봉", 19900 };
    ProductInfo yourProduct = { 4797284, "딸기 타르트", 10000 };

    productSwap(&myProduct, &yourProduct);  // 주소 전달

    // swap 후: myProduct에는 딸기 타르트 정보가 들어있음
    printf("제품 번호 : %d\\n", myProduct.num);
    printf("제품 이름 : %s\\n", myProduct.name);
    printf("제품 가격 : %d원\\n", myProduct.cost);
}
  • 포인터로 받아야 원본 수정이 가능하다.
    • 서로 다른 변수지만 같은 주소를 사용하게 된다.
  • 구조체 전체 요소를 한번에 넘길 수 있다.
  • 임시변수가 필요하다.
    • 구조체 크기만큼 메모리를 사용한다.

값 전달 - call by value

struct Time {
    int h, m, s;  // 시, 분, 초
};

int totalSec(Time t) {  // 구조체 전체가 복사됨
    return 60 * 60 * t.h + 60 * t.m + t.s;
}

int main() {
    Time t = { 1, 22, 48 };

    printf("%d\\n", totalSec(t));  // 4968초
}
  • 함수 호출 시 구조체 전체 복사
  • 원본 데이터 보호 (수정 불가)
  • 작은 구조체에 적합
    • 매개변수에서 같은 구조체의 변수가 생성되기 때문에 큰 구조체는 오버헤드가 일어날 수 있다.
  • 메모리와 시간 소비 (큰 구조체는 비효율적)

주소 전달 - call by address

struct Time {
    int h, m, s;
};

int totalSec(Time* t) {  // 주소만 전달 (4 또는 8바이트)
    return 60 * 60 * t->h + 60 * t->m + t->s;
}

int main() {
    Time t = { 1, 22, 48 };

    printf("%d\\n", totalSec(&t));  // 4968초
}
  • 포인터만 전달 (효율적)
  • 원본 수정 가능 (주의 필요)
  • 큰 구조체에 필수
  • 화살표 연산자로 멤버 접근

값과 주소 선택 기준

  • 작은 구조체 + 원본 보호 필요 → 값 전달
  • 큰 구조체 → 주소 전달
  • 원본 수정 필요 → 주소 전달
  • 읽기만 필요 + 큰 구조체 → const 포인터

구조체에 함수 넣기

struct Time {
    int h, m, s;

    // 구조체 안에 함수 정의 (멤버 함수)
    int totalSec() {
        return 60 * 60 * h + 60 * m + s;  // 멤버 직접 접근
    }
};

int main() {
    Time t = { 1, 22, 48 };

    printf("%d\\n", t.totalSec());  // 객체의 함수 호출
}
  • 멤버 함수는 해당 함수가 있는 멤버 변수만 접근할 수 있다.