c

배열, 포인터와 구조

haniru 2025. 11. 11. 22:50

2차원 배열과 배열 포인터

포인터 변수는 메모리 주소를 값으로 저장한다. 이 주소는 다른 데이터(변수, 배열 등)가 저장된 위치를 가리키는 역할을 한다.

int ary[3][4]: 2차원 배열에서 배열의 이름 ary는 첫 번째 행 배열의 주소를 나타낸다. 즉 ary의 타입은 "int 4개 짜리 배열을 가리키는 포인터" 이다

int (*pa) [4]: 배열 포인터이다. (*pa)에 괄호가 있으면 pa가 포인터임을 먼저 선언한다. pa는 int 4개짜리 배열을 가리키는 포인터이다 (만약 괄호가 없으면 int *pa[4]는 포인터 4개짜리 배열이 된다)

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

// 함수 선언: 배열 포인터를 매개변수로 받음
void print_ary(int (*pa)[4]);

int main() {
    int ary[3][4] = {
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12}
    };
    int (*pa)[4]; 
    int i, j;

    pa = ary;

    // main 함수 내 출력 (작동하는 부분)
    printf("--- main 함수 내 출력 ---\\n");
    for (i = 0; i < 3; i++) {
        for (j = 0; j < 4; j++) {
            printf("%5d ", pa[i][j]);
        }
        printf("\\n");
    }
    printf("------------------------\\n");

    // print_ary 함수 호출
    print_ary(ary); // 'ary'의 시작 주소를 함수에 전달

    return 0;
}

// 함수 정의: 전달받은 포인터 'pa'를 사용하여 배열 요소에 접근
void print_ary(int (*pa)[4]) {
    printf("--- print_ary 함수 내 출력 ---\\n");
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 4; j++) {
            // 전달받은 포인터 'pa'를 사용하여 요소에 접근
            printf("%5d ", pa[i][j]); 
        }
        printf("\\n");
    }
}

 

1차원 배열을 함수에 전달

C 언어에서 배열을 함수로 전달할 때 배열 전체가 복사되는 것이 아니라 배열의 시작주소만 함수로 전달된다

print_array 내에서 포인터 ary는 마치 배열 이름처럼 사용되는데, 컴파일러는 ary[i]를 포인터 연산 *(ary+i)로 해석하여 배열의 i번째 요소에 접근한다

print_array는 전달받은 배열의 실제 크기가 얼마인지 알 수 없어 배열의 크기도 같이 넘겨주는게 좋다

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

void print_array(char* ary, int size);
int main() {
	char arr[5] = { 'A', 'B', 'C', 'D', 'E' };
    int arr_size = sizeof(arr) / sizeof(arr[0]);
	print_array(arr, arr_size);

	return 0;
}

void print_array(char* ary, int size) {
	for (int i = 0; i < size; i++) {
		printf("%c ", ary[i]);
	}
}

 

C 프로그램의 메모리 위치

동적 메모리 할당(힙)

malloc: 메모리 할당을 요청한다. 요청한 크기(sizeof(...)) 만큼 힙 영역에서 메모리를 할당하고 할당된 공간의 시작주소를 반환한다

malloc은 void*형 포인터를 반환하기 때문에 (int*)나 (double*)로 형 변환을 해주어야 한다

또 malloc으로 할당한 후 free(pi)나 free(pd)를 안하면 메모리가 계속 힙에 남아있게 된다(메모리 누수). 프로그램이 종료될 때까지 메모리가 반환되지 않아, 장기 실행 프로그램의 경우 메모리 부족을 유발할 수 있다. 이는 충돌로 이어지거나 운영체제가 프로그램을 강제 종료시켜 잘못된 연사으로 종료될 수 있다.

기계어 코드 자체는 코드영역에 저장된다. 함수 호출과 지역변수에 스택이 저장된다.

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
//#include <stdlib.h> // malloc, free
#include <memory.h> // malloc

int total; // 데이터 영역

int main() {
    int* pi; // 힙
    double* pd; // 힙
    int a, b; // 스택

    pi = (int*) malloc(sizeof(int)); // 4바이트짜리 공간 한개 만들겠다. int 형을 만들겠다. (int*): 형 변환
    pd = (double*)malloc(sizeof(double));

    printf("----Heap----\\n");
    printf("%p\\n", pi); // 힙에 있다
    printf("%p\\n\\n", pd);

    printf("----Stack----\\n");
    printf("%p\\n", &a); // a와 b는 스택에 있다
    printf("%p\\n\\n", &b);

    printf("----String, Global----\\n");
    printf("%p\\n", "Hello"); // 코드 영역 (global 변수와 비슷한 위치에 있음)
    printf("%p\\n", &total);

    return 0;
}
메모리 영역 저장되는 데이터 관리 방법 코드 예시
스택 (Stack) 지역 변수, 함수의 매개변수, 복귀 주소 자동 (함수 호출 시 생성, 종료 시 소멸) int a, b;
힙 (Heap) 프로그래머가 원하는 크기만큼 동적 할당하는 데이터 수동 (malloc, free를 사용해 관리) pi = malloc(...)
데이터 영역 전역 변수, 정적 변수 (static) 프로그램 시작 시 할당, 종료 시 소멸 int total;
코드 영역 실행 가능한 기계어 코드, 문자열 상수 읽기 전용 "Hello"

 

동적 메모리 할당 및 해제

malloc: 프로그램 실행 도중 필요한 크기 만큼 힙 메모리 영역에 공간을 할당한다

free()를 호출하지 않으면 해당 메모리 공간은 메모리 누수 상태가 된다. pi, pd가 스택에서 소멸되더라도, 할당된 힙 메모리는 컴퓨터가 꺼지 전까지 해제되지 않고 계속 점유한다.

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h> // malloc, free

int total;

int main() {
    int* pi;
    double* pd;

    pi = (int*)malloc(sizeof(int));  // (int*)malloc(sizeof(4));
    //pi = NULL;
    if (pi == NULL) { // NULL 0
        printf("메모리 에러/부족!!");
        exit(1);
	}

    pd = (double*)malloc(sizeof(double)); // (double*)malloc(sizeof(8));

    *pi = 10; // stack 메모리가 아닌 heap 메모리 공간에 값 삽입
    *pd = 3.4; // heap에 값 삽입

    printf("%d\\t%.2lf\\n", *pi, *pd);

    // free를 안 해주면 컴퓨터 껐다 키기 전까지 메모리 해제가 안됨 (메모리 누수 발생)
    free(pi);
    free(pd);

    return 0;
}

참고로 NULL은 0을 뜻한다

 

평균나이 구하기

pi = (int*)malloc(5 * sizeof(int)): 힙 메모리 영역에 5개의 int를 저장할 수 있는 연속된 공간. int pi[5] 와 기능적으로 유사하게 5개의 int 공간을 확보하지만 메모리 위치가 힙이라는 차이가 있다. pi는 힙에 할당된 5개의 int 공간 중 첫 번째 요소의 주소를 가리킨다.

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h> // malloc, free

int total;

int main() {
    int* pi;
    int sum = 0;
    // 배열을 heap에 할당
    pi = (int*)malloc(5 * sizeof(int)); // int pi[5] 랑 비슷 
    printf("다섯명의 나이를 입력하세요: ");

    for (int i = 0; i < 5; i++) {
        scanf("%d", &pi[i]); // 배열포인터 pi는 배열 이름처럼 사용된다. pi[i]는 heap에 할당된 i번째 정수 공간 자체를 의미한다.
        sum += pi[i]; // heap에 있는 변수의 값을 가져온다
    }
    printf("평균 : %.1lf\\n", sum / 5.0);
    free(pi);

    return 0;
}

 

스택, 힙 테스트

try_stack_alloc(): 스택 오버플로우로 인해 프로그램이 즉시 종료되거나 오류 메시지를 출력한다

try_heap_alloc(): 성공적으로 메모리를 할당하고 값을 초기화한 뒤 메모리를 해제한다

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifdef _MSC_VER
#include <windows.h>
#endif

// STACK_ELEMS의 값 * int타입 크기 = 3000000 * 3 바이트 = 1200000 바이트 => 약 1.2MB
#define STACK_ELEMS 300000  

// 힙에 할당할 정수 개수 (동일 크기 비교)
#define HEAP_ELEMS  STACK_ELEMS

// 스택 할당 시도 — 매우 큰 로컬 배열을 선언하여 스택 오버플로우를 유발하려는 목적
void try_stack_alloc(void)
{
    printf("스택에 큰 배열을 선언합니다: %d elements (약 %zu bytes)\\n",
        STACK_ELEMS, (size_t)STACK_ELEMS * sizeof(int));

    // 의도적으로 큰 로컬 배열 선언 — 종종 프로그램 즉시 크래시
    // 주의: 이 함수 호출은 프로세스를 종료시킬 수 있음
    int big_array[STACK_ELEMS]; // <- stack 공간에 할당

    // 배열 초기화 (디버그 목적)
    for (int i = 0; i < STACK_ELEMS; ++i) {
        big_array[i] = i;
    }

    printf("스택 할당 성공 (예상치 못한 경우). 첫/마지막 값: %d / %d\\n",
        big_array[0], big_array[STACK_ELEMS - 1]);
}

// 힙 할당 예제 — malloc 사용, 실패 검사 포함
void try_heap_alloc(void)
{
    printf("힙에서 메모리를 할당합니다: %d elements (약 %zu bytes)\\n",
        HEAP_ELEMS, (size_t)HEAP_ELEMS * sizeof(int));

    int* p = (int*)malloc((size_t)HEAP_ELEMS * sizeof(int));
    if (!p) {
        fprintf(stderr, "malloc 실패: 메모리 할당 불가\\n");
        return;
    }

    // 초기화 및 검증
    for (int i = 0; i < HEAP_ELEMS; ++i) p[i] = i;
    printf("힙 할당 성공. 첫/마지막 값: %d / %d\\n", p[0], p[HEAP_ELEMS - 1]);

    free(p);
}

int main(int argc, char** argv)
{
    printf("테스트 프로그램 시작\\n");
    printf("사용법: 실행파일 stack  --> 스택 할당 시도(크래시 가능)\\n");
    printf("       실행파일 heap   --> 힙(malloc)으로 할당\\n\\n");

    if (argc < 2) {
        printf("옵션을 입력하세요 (stack 또는 heap)\\n");
        return 0;
    }

    if (strcmp(argv[1], "stack") == 0) {
        printf("[주의] stack 옵션은 스택 오버플로우로 인해 프로그램이 비정상 종료될 수 있습니다.\\n");
        try_stack_alloc();
    }
    else if (strcmp(argv[1], "heap") == 0) {
        try_heap_alloc();
    }
    else {
        printf("알 수 없는 옵션: %s\\n", argv[1]);
    }

    printf("프로그램 종료\\n");
    return 0;
}


Visual Studio 의 프로젝트 > (프로젝트명) 속성 > 디버깅 > 명령인수를 변경하면 argv 매개변수 값으로 해당 코드를 테스트 할 수 있다

 

참고로 스택 배열 선언을 에러없이 하싶다면 다음과 같이 설정하면 된다 (2000000 바이트는 2MB이고 1.2MB보다 크기 때문에 STACK_ELEMS로 선언된 배열을 충분히 수용할 수 있다)

 

 

calloc과 realloc

calloc: 메모리를 할당하고 동시에 할당된 공간을 0으로 초기화 한다. ptr = calloc(num_elements, element_size)

realloc: 이미 할당된 메모리 블록의 크기를 재조정 한다 (늘리거나 줄일 수 있음). new ptr = realloc(old_ptr, new_size)

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>

int main() {
	int* pi;
	int size = 5;
	int count = 0;
	int num;
	int i;

	pi = (int*)calloc(size, sizeof(int));
	while (1) {
		printf("양수만 입력하세요 => ");
		scanf("%d", &num);
		if (num <= 0) break;

		if (count == size) { // 5개 이상 입력시 realloc 으로 공간 늘리기
			size += 5;
			pi = (int*)realloc(pi, size * sizeof(int));
		}
		pi[count++] = num;
	}

	for (i = 0; i < count; i++) {
		printf("%5d\\n", pi[i]);
	}
	free(pi);

	return 0;
}

 

메모리 영역

코드, 스택, 기타 데이터, 힙 영역이 있다.

힙 영역은 원하는 크기만큼 할당하지 못할 수 있다 (연속된 공간 할당하지 못할 수 있음)

 

동적 할당을 사용한 문자열 처리

char temp[80]: 사용자의 입력을 임시로 저장하는 버퍼. main 함수내의 스택 영역에 할당됨

char* str[3]: char 포인터 3개를 저장하는 배열. 이 배열 자체는 main 함수 내의 스택영역에 할당되며, 각 요소는 힙 메모리 주소를 저장한다.

str[i] = (char*) malloc(strlen(temp) + 1): 힙에 할당

free(str[i]): 메모리 누수 방지

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
	char temp[80];
	char* str[3];
	
	for (int i = 0; i < 3; i++) {
		printf("문자열을 입력하세요 : ");
		fgets(temp, sizeof(temp), stdin);
		str[i] = (char*) malloc(strlen(temp) + 1); // +1 은 '\\0' 문자 들어가는 곳
		strcpy(str[i], temp);
	}

	for (int i = 0; i < 3; i++) {
		printf("%s\\n", str[i]);
		free(str[i]); // 메모리 해제, 메모리 해제하지 않으면 힙에 지워지지 않는 데이터를 쌓은 것이다. 컴퓨터 껐다 킬때까지 안 사라진다.
	}

	return 0;
}

 

동적 배열에 문자열 입력받고 출력하기

 

문자 입력 및 전처리

char temp[80]: 스택에 할당된 임시공간

fgets(temp, sizeof(temp), stdin): 버퍼 오버플로우를 막기 위해 안전하지만 입력시 개행문자(\n)도 함께 저장한다.

size_t n_index = strcspn(temp, "\\n"): temp에서 개행문자 \n이 처음 나타나는 위치를 찾는다

temp[n_index] = 0: 찾아낸 \n 위치에 \0을 넣어 문자열을 끝내버린다

 

동적 할당 및 저장

char* str[21]: 스택에 할당된 21개의 문자열 포인터 배열. 각 포인터는 힙에 할당된 문자열의 주소를 저장

str[i] = (char*)malloc(strlen(temp) + 1): 입력된 문자열 길이에 맞는 크기만큼 힙에 메모리를 동적으로 할당

strcpy(str[i], temp): 스택의 임시버퍼에 있는 문자열이 힙의 영구적인 공간으로 복사됨

 

print_str 함수와 이중 포인터

char** ps: 문자열 포인터(char*)를 가리키는 포인터 (이중포인터) 함수가 호출될 때 main 함수의 str 배열 이름(str[0]의 주소, 즉 char**)이 전달된다

while(*ps != NULL): *ps는 현재 ps가 가리키는 포인터 배열의 요소(힙에 있는 문자열 주소)이다. 이 값이 NULL인지 확인해 문자열의 끝을 판단한다. ps++는 포인터 ps를 다음 문자열 포인터가 있는 메모리 주소로 이동시킨다.

 

메모리 해제

free(str[i]): 반복문을 통해 배열 str에 저장된 모든 주소를 따라가서 힙에 할당된 각 문자열 공간을 개별적으로 해제한다 (동적할당된 메모리 관리)

str[21] 자체는 스택변수여서 main 함수가 종료될 때 자동으로 소멸된

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void print_str(char** ps);

int main() {
	char temp[80];
	char* str[21] = { 0 };
	int i = 0;

	while (i < 20) {
		printf("문자열을 입력하세요: ");
		fgets(temp, sizeof(temp), stdin);

		// \\n 문자의 인덱스 찾기
		size_t n_index = strcspn(temp, "\\n");
		//printf(">>> strcspn(temp, \\"\\\\n\\") 결과: %zu (개행 문자 \\\\n의 인덱스)\\n", n_index);

		// 개행문자 \\n 제거 - \\0으로 대체해서 문자열 끝내기
		temp[n_index] = 0;
		int compare_result = strcmp(temp, "end");
		//printf(">>> strcmp(temp, \\"end\\") 결과: %d (0이면 같음)\\n", compare_result);

		if (compare_result == 0) break;
		str[i] = (char*)malloc(strlen(temp) + 1);
		strcpy(str[i], temp);
		i++;
	}

	print_str(str);

	for (i = 0; str[i] != NULL; i++) {
		free(str[i]);
	}
	return 0;
}

void print_str(char** ps) {
	while (*ps != NULL) {
		printf("%s\\n", *ps);
		ps++;
	}
}

 

구조체로 길동 학생의 학번과 성적 정보 저장하고 출력하기

구조체: 서로 다른 자료형을 하나로 묶어 새로운 사용자 정의 자료형을 만드는 문법. student 구조체는 하나의 int와 하나의 double을 포한하는 하나의 데이터 묶음이다.

struct student: 새로 정의된 자료형의 이름이다

struct student gildong: 정의된 struct student 타입을 사용해 gildong 이라는 이름의 변수를 선언한다

gildong 변수는 num (4바이트)과 grade (8바이트)를 저장할 수 있는 공간을 스택 메모리에 할당 받는다 (패딩을 고려하면 실제 크기는 더 클 수 있음)

접근 연산자(.): 구조체 변수 내부 멤버에 접근 가능

원시 자료형: int, double / 복합 자료형: 구조

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct student {
	int num; // 학번
	double grade; // 학점
};

// primitive type
int main() {
	struct student gildong; // 자료형
	gildong.num = 2; // 구조체 초기화
	gildong.grade = 3.4;

	printf("길동이의 학번 :  %d\\n", gildong.num); // 구조체 출력
	printf("길동이의 성적 :  %.1lf\\n", gildong.grade);
	return 0;
}

 

구조체에 자기소개 정보 입력하기

구조체는 단순히 멤버 변수 크기를 합한 것보다 클 수 있는데, 이는 메모리 패딩 규칙 대문이다 (CPU가 메모리에서 데이터를 효율적으로 빠르게 읽기 위해, 구조체의 멤버들은 특정 크기 (일반적으로 가장 큰 멤버의 크기)의 배수 주소에 정렬되어야 한다)

yuni.intro = (char*)malloc(size * sizeof(char)): malloc은 힙 영역에서 원하는 크기의 연속된 메모리 공간을 요청한다

malloc: 할당된 메모리 블록의 시작주소를 void* 포인터로 반환한다. 어떤 타입의 포인터에도 대입할 수 있도록 범용적인 타입으로 반환되는 것이다. 그래서 실제 사용할 타입인 char*로 저장하기 위해 (char*)로 형변환(캐스팅)을 사용한다

한글 처리: 한글은 보통 2바이트의 공간을 사용하기 때문에 80바이트의 공간은 최대 40자까지의 한글 문자열을 저장할 수 있다

문자열 저장: fgets를 사용해 힙에 할당된 80바이트 공간(yuni.intro)에 사용자 입력을 저장한다

메모리 해제: main 함수가 종료되기전에 free(yuni.intro)를 호출해 힙 메모리를 해제해야한다. 현재 코드에선 메모리 누수가 발생할 수 있음.

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct profile {
	char name[20];
	int age;
	double height;
	char* intro;
};

int main() {
	struct profile yuni;
	int size = 80;

	// 변수 초기화
	strcpy(yuni.name, "홍길동");
	yuni.age = 17;
	yuni.height = 175.5;
	yuni.intro = (char*)malloc(size * sizeof(char));

	if (yuni.intro == NULL) {
		printf("메모리 공간이 없습니다.\\n");
		exit;
	}
	printf("자기소개를 입력해주세요 : ");
	fgets(yuni.intro, size, stdin);

	printf("이름: %s\\n", yuni.name);
	printf("나이: %d\\n", yuni.age);
	printf("키: %.2lf\\n", yuni.height);
	printf("자기소개: %s\\n", yuni.intro); // ctrl + space

	return 0;
}

 

구조체 멤버 타입 메모리 영역 설명
name, age, height char[20], int, double 스택 구조체 변수 yuni가 스택에 할당될 때 이 멤버들의 공간도 함께 할당됨.
intro char* 스택 (포인터 자체) 이 포인터가 가리키는 실제 문자열 데이터는 malloc에 의해 에 할당됨.

 

구조체 안의 구조

내부 구조체: profile

외부 구조체: student

C와 C++에서는 선언과 동시에 메모리(스택 메모리)가 할당된다. yuni 변수는 profile 구조체의 모든 멤버(age, height)와 student의 나머지 멤버(id, grade)를 위한 공간을 연속적으로 갖게 된다.

외부 접근: yuni.pf

내부 접근: yuni.pf.age

복잡한 데이터를 논리적 단위로 묶어 관리할 때 효율

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct profile {
	int age;
	double height;
};

struct student {
	struct profile pf;
	int id;
	double grade;
};

// inner class, outer class 처럼 구조체도 비슷
// c,c++ new를 안해도 생성되는데 자바에서는 안그럼 -> c와 c++은 선언과 동시에 만들어진다
int main() {
	struct student yuni;

	yuni.pf.age = 17;
	yuni.pf.height = 164.5;
	yuni.id = 315;
	yuni.grade = 4.3;

	printf("나이 : %d\\n", yuni.pf.age);
	printf("키 : %.1lf\\n", yuni.pf.height);
	printf("학번 : %d\\n", yuni.id);
	printf("학점 : %.1lf\\n", yuni.grade);

	return 0;
}

 

구조체로 가장 큰 성적을 가진 학생 출력하기

c 언어에서 같은 타입의 구조체 변수끼리 대입 연산자를 사용하면, 소스 구조체(s1)의 모든 멤버 값이 대상 구조체(max)의 동일 멤버에 자동으로 복사된

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct student {
	int id;
	char name[20];
	double grade;
};

int main() {
	//struct student s1 = { 315, "홍길동", 2.6 }; // 선언과 동시에 초기화
	//struct student s2 = { 316, "이순신", 3.7 };
	//struct student s3 = { 317, "세종대왕", 4.4 };

	struct student s1 = { 315, "홍길동", 2.6 }, 
		s2 = { 316, "이순신", 3.7 }, 
		s3 = { 317, "세종대왕", 4.4 };

	struct student max;

	// 큰 성적을 가진 학생을 max에 저장
	// 타입이 같다: 공간의 크기가 같다
	max = s1;

	if (s2.grade > max.grade) {
		max = s2;
	}

	if (s3.grade > max.grade) {
		max = s3;
	}

	printf("학번 : %d\\n", max.id);
	printf("이름 : %s\\n", max.name);
	printf("학점 : %.1lf\\n", max.grade); // ctrl + space

	return 0;
}

 

구조체 함수 반환 및 전달 방식 정리

exchange(pass-by-value): main 함수에서 robot 구조체의 모든 멤버 값이 복사되어 함수 내의 지역변수 robot 에 전달된다. 함수 내에서 변경된 내용은 복사된 지역 변수에만 적용되고, 함수가 변경된 새로운 구조체 값을 반환한다. main 함수에서 이 값을 원본 변수에 다시 대입해야 원본이 변경된다

exchange_ptr(pass-by-reference): main 함수에서 robot 구조체의 주소가 전달된다. -> 연산자를 사용해 포인터가 가리키는 원본 메모리 공간의 값을 직접 변경한다 (temp와 robot의 주소가 같다). 원본 구조체의 주소를 반환하고 이 반환값을 원본에 대한 참조를 유지한다.

exchange_ptr2(pass-by-reference): robot 구조체의 주소가 전달된다. exchange_ptr 과 마찬가지로 -> 연산자를 사용해 원본 메모리의 값을 직접 변경하고, 반환값이 void 이기 때문에 함수를 호출하는 것만으 원본 변수의 값이 변경된다.

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct vision {
	double left;
	double right;
};

struct vision exchange(struct vision robot);
struct vision* exchange_ptr(struct vision* robot);
void exchange_ptr2(struct vision* robot);

int main() {

	struct vision robot;

	printf("시력 입력 : ");
	scanf("%lf %lf", &robot.left, &robot.right);

	// exchange 함수는 값을 복사하여 반환하고, 그 값을 robot에 다시 대입한다
	//robot = exchange(robot);

	// exchange_ptr 함수는 포인터(주소)를 통해 원본 robot의 값을 직접 변경한다
	struct vision* temp = exchange_ptr(&robot); // temp 와 robot의 주소가 같다

	//printf("temp 변수의 값 (robot의 주소): %p\\n", (void*)temp);
	//printf("robot 변수의 실제 주소: %p\\n", (void*)&robot);
	
	// void 형
	exchange_ptr2(&robot);

	printf("바뀐 시력: %.1lf %.1lf", robot.left, robot.right);
	
	return 0;
}

// 포인터를 사용하여 원본 구조체의 값을 직접 변경
struct vision* exchange_ptr(struct vision* robot) {
	double temp = robot->right;
	robot->right = robot->left;
	robot->left = temp;

	return robot;
}

// 포인터를 사용하여 원본 구조체의 값을 직접 변경
void exchange_ptr2(struct vision* robot) {
	double temp = robot->right;
	robot->right = robot->left;
	robot->left = temp;
}

// 구조체 값을 복사하여 전달받아 값을 변경한 후 변경된 구조체 '값'을 반환
struct vision exchange(struct vision robot) {
	double temp = robot.right;
	robot.right = robot.left;
	robot.left = temp;

	return robot;
}

 

 

wsl 실습

Power Shell

wsl --list --verbose
wsl --version
wsl -l -v
wsl -d Ubuntu # d:배포판

 

리눅스(우분투)

cd
pwd # 리눅스 경로 명령어
ls
ls -a # 숨어있는 파일

sudo apt update # repository 정리
sudo vi /etc/apt/sources.list.d/ubuntu.sources
	# URLs: http://ftp.daumkakao.com/ubuntu/ 로 변경
sudo apt update # repository(원격저장소) 갱신 - 카카오 미러 사이트로 갱신
gcc
g++
make
sudo apt install build-essential # C/C++ 컴파일 및 개발에 필요한 핵심 도구들(gcc, g++, make)를 한번에 설치
gcc
g++
make

mkdir work # 디렉터리(폴더) 만들기
ls
cd work
mkdir basic
pwd
cd /home/robot/work/basic
vi hello.c # nano hello.c 도 됨
	#include <stdio.h>
	int main() {
		printf("Hello World\\n");
		return 0;
	}
ls
gcc hello.c # C 소스 파일을 컴파일. a.out이라는 이름의 실행파일이 생성됨
ls
./a.out # 현재 디렉터리 ./ 에 있는 실행 파일 a.out을 실행
gcc -c hello.c # 컴파일만 수행하고 링킹은 하지 않아 오브젝트 파일(.o)만 생성됨
ls -l # 용량(바이트)가 보임
gcc -o hello hello.c # 소스 파일 hello.c를 컴파일 하여 -o 옵션 뒤에 지정된 이름(hello)의 실행 파일 생성. 목적파일 만들면서 실행파일도 만듦
ls -l # 파일의 상세 정보 출력
./hello # 생성된 hello 실행 파일 실행
exit # 현재 셸 세션 종료
exit

 

Power Shell

power shell 에서
wsl -d Ubuntu

 

sudo vi /etc/sources.list.d/ubuntu.sources

1. 첫 번째 섹션 (일반 저장소)

URIs: http://ftp.daumkakao.com/ubuntu/
Suites: noble noble-updates noble-backports
Components: main universe restricted multiverse
  • 이 설정은 Daum Kakao 미러 서버에서 일반적인 패키지(새 버전, 업데이트, 백포트 등)를 가져옵니다.
  • Suites에는 기본 릴리스 이름인 **noble**과 그 업데이트 버전인 **noble-updates, noble-backports**가 포함되어 있습니다.

2. 두 번째 섹션 (보안 업데이트 저장소)

URIs: http://security.ubuntu.com/ubuntu/
Suites: noble-security
Components: main universe restricted multiverse
  • 이 설정은 Ubuntu 공식 보안 서버(security.ubuntu.com)에서 패키지를 가져옵니다.
  • Suites에 **noble-security**만 있는 것은 보안 업데이트를 위한 것입니다.
  • 보안 업데이트 저장소의 목적은 오직 현재 릴리스(noble)에 대한 보안 패치를 제공하는 것이므로, noble-updates나 noble-backports와 같은 일반적인 업데이트 버전은 포함할 필요가 없습니다.

결론적으로, noble-security는 특정 릴리스(여기서는 noble)의 보안 패키지를 모아둔 저장소의 이름을 지칭하며, 두 번째 섹션은 이 보안 저장소를 명시적으로 사용하도록 설정된 것입니다. 이 필드를 변경하면 보안 업데이트를 받지 못하게 됩니다.

 

Visual Studio code 다운로드

다음과 같이 체크

 

플러그인 설치

이거는 아래 app01.c 만들면 설치하라고 뜬다. 이 Extension Pack을 설치해야 C/C++ 코드의 빌드, 실행, 디버깅 기능을 사용할 수 있다.

 

wsl 플러그인 설치 후 wsl 창 열기

ctrl + shift + p

wsl 검색 후 connect to wsl new Window 클릭

 

app.c 만들기

터미널에 다음과 같이 입력

ls

cd work/basic

code app01.c

#include <stdio.h>

int main() {
    int a=10, b=20;
    printf("result: %d\n", a+b);
    return 0;
}

코드 저장(ctrl+s)

sudo apt install gdb # vs code로 코드 디버깅/실행 하기 위해 설치해야 함

sudo apt install gdb 안하면 VS Code는 컴파일러와 디버거가 준비되지 않았다고 판단하여 Run C/C++ File 을 시도하면 실행을 진행할 수 없다

 

 

sudo apt install gdb 후 디버깅을 시작하면, 아래 사진과 같이 올바른 디버그 구성 옵션을 제시하여 사용자가 바로 디버깅을 시작할 수 있게 된다.

 

  • C/C++: gcc build and debug active file:
    • Detected Task (compiler: /usr/bin/gcc)라고 명시되어 있듯이, 시스템에 설치된 표준 GCC 컴파일러를 사용하여 현재 파일을 빌드하고 GDB로 디버깅하는 설정입니다.
  • C/C++: gcc-13 build and debug active file:
    • Detected Task (compiler: /usr/bin/gcc-13)라고 명시되어 있습니다. 만약 시스템에 **특정 버전(GCC-13)**의 컴파일러가 추가로 설치되어 있다면, 이를 사용하여 빌드하고 GDB로 디버깅하는 설정을 별도로 제시합니다.

 

 

 

wsl 참고

1. 콘솔 환경 및 명령어

환경 설명 주요 용도
CMD (Command Prompt) 전통적인 Windows 콘솔 환경입니다. 기본 Windows 시스템 관리 및 간단한 배치 파일 실행. help 명령어로 내장 명령어를 확인할 수 있습니다.
PowerShell Windows의 고급 명령어 셸이자 스크립트 언어입니다. 시스템 자동화, 관리, 네트워크 작업 등. 리눅스 명령어와 유사하거나 호환되는 기능을 많이 제공합니다 (실제 리눅스 셸은 아님).

2. 가상화 및 컨테이너 기술

기술 구분 특징
Hyper-V / VMware 전통적인 가상화 (VM) 하드웨어를 완벽하게 에뮬레이션하여 **전체 OS(게스트 OS)**를 구동합니다. 무겁고 리소스 소모가 큽니다.
WSL (Windows Subsystem for Linux) 경량 가상화 Windows 위에서 별도의 VM 없이 리눅스 환경을 사용할 수 있게 해줍니다. 리눅스 명령어에 익숙해지는 데 매우 유용합니다.
Docker 컨테이너화 OS의 커널을 공유하며 애플리케이션과 그 종속성만 격리하여 실행합니다 (Process 방식). VM보다 훨씬 가볍고 빠르며, 이식성이 높아 현재 개발 및 배포의 필수 기술로 자리 잡았습니다. (WSL과 Docker는 기술적 목표는 다르지만, 경량화된 리눅스 환경을 제공한다는 점에서 비슷하게 느껴질 수 있습니다.)

3. 개발 방향성 (리눅스 환경의 중요성)

  • 리눅스 명령어의 숙달: WSL을 통해 리눅스 명령어에 익숙해지는 것이 중요합니다.
  • 리눅스 환경 중심 개발: 현재 클라우드 환경과 대다수의 AI(인공지능) 및 빅데이터 플랫폼이 리눅스 기반으로 구축되어 있습니다.

'c' 카테고리의 다른 글

파일처리  (0) 2025.11.13
구조체  (0) 2025.11.12
이중포인터 N차원 배열  (1) 2025.11.10
문자열  (0) 2025.11.07
포인터  (0) 2025.11.06