레지스터 변수
레지스터변수는 cpu 내부에 있는 레지스터라는 매우 빠른 기억장치에 변수를 할당하도록 컴파일러에게 요청하는 변수이다
레지스터는 메인 메모리(RAM)보다 훨씬 빠르게 CPU가 접근할 수 있어 변수를 레지스터에 저장하면 연산 속도를 높일 수 있다
하지만 레지스터는 메모리가 아니므로 고유한 메모리 주소를 가지지 않고 & 연산자를 사용해 레지스터 변수의 주소를 얻을 수 없다
#include <stdio.h>
int main(void) {
register int i;
int sum = 0;
for (i = 0; i < 1000000; i++) {
sum += i;
}
printf("Sum: %d\n", sum);
// printf("i의 주소: %p\n", &i); // 컴파일 에러 발생 (Cannot take address of register variable)
return 0;
}
CPU 연산 장치, 레지스터, 메인 메모리 관계도
| 요소 | 위치 | 속도 | 용량 | 용도 |
| 레지스터 | CPU 내부 | 매우 빠름 | 매우 작음 (몇 바이트) | 현재 CPU가 연산에 사용할 임시 데이터 저장 |
| 메인 메모리 (RAM) | CPU 외부 | 느림 | 큼 (GB 단위) | 실행 중인 프로그램 코드 및 일반 변수 데이터 저장 |
지역 변수, 정적 지역 변수, 전역 변수
1. 지역 변수 (Local Variable)
선언 위치: 코드 블록 (주로 함수 내부 {}) 내부.
범위 (Scope): 선언된 {} 블록 내부에서만 유효.
생명주기 (Lifetime): 함수나 블록이 실행되는 시점에 스택(Stack) 메모리에 할당되었다가, 해당 블록을 벗어나면 메모리에서 자동으로 해제되어 소멸.
2. 정적 지역 변수 (static Local Variable)
선언 위치: 코드 블록 내부 (앞에 static 키워드 사용).
범위 (Scope): 선언된 {} 블록 내부에서만 유효 (지역 변수와 동일).
생명주기 (Lifetime):
누적 (값 유지): 프로그램이 시작될 때 데이터(Data) 영역에 할당되고, 프로그램이 종료될 때까지 메모리에 존재.
함수가 호출되어도 소멸되지 않고, 함수가 다시 호출되면 이전의 값이 누적되어 유지.
초기화: 최초 1회만 실행 시에 초기화.
3. 전역 변수 (Global Variable)
선언 위치: 함수 외부 (가장 바깥쪽).
범위 (Scope): 선언된 파일 전체 (다른 파일에서 extern으로 참조 가능).
생명주기 (Lifetime): 프로그램이 시작될 때 데이터(Data) 영역에 할당되고, 프로그램이 종료될 때까지 메모리에 존재
정적 전역 변수 (static Global Variable): 전역 변수 앞에 static을 붙이면 선언된 파일 내에서만 유효하며 다른 파일에서는 접근할 수 없도록 범위를 제한. 생명주기는 일반 전역 변수와 같다.
참조에 의한 호출
참조에 의한 호출을 사용하지 않고 call by value 만 사용한다면, a 값이 변하지 않는다
static int a 를 사용하더라도 add_ten 을 호출할 때 값이 복사되어 전달되고, add_ten 함수에 있는 a 매개변수는 함수가 종료되면 사라지기 때문에 main 의 a 값은 변하지 않는다.
#include <stdio.h>
void add_ten(int a); // call by value: 호출 시 원본 'a'의 값(10)이 복사되어 전달됨
int main()
{
// 'static'은 이 변수의 수명(프로그램 종료까지)을 지정하며,
// 함수 호출 시 값 복사에는 영향을 주지 않음.
static int a = 10; // main 스택 프레임에 있는 원본 'a'
add_ten(a);
// add_ten 함수는 원본 'a'를 변경하지 못했으므로 10이 출력됨
printf("a : %d\\n", a);
return 0;
}
void add_ten(int a) { // 이 'a'는 main의 'a'와 별개의 새로운 지역 변수 (복사본)
// 매개변수는 함수가 종료되면 사라짐 (해당 스택 프레임 해제)
// 따라서 이 내부에서 값을 +10 하더라도 원본 main의 'a'는 변경되지 않음.
a = a + 10; // 복사본 'a'의 값이 20이 됨.
}
참조에 의한 호출(call by reference)을 사용하게 되면 함수에서 a값을 변경할 수 있다
순서
초기상태: int a = 10; - main 스택에 변수 a가 생성되고 값 10으로 초기화 된다
함수 호출: add_ten(&a) - 변수 a의 주소가 복사되어 add_ten 함수의 매개변수 포인터 pa에 전달된다
함수 실행: *pa = *pa + 10; - pa가 가리키는 주소(a의 주소)로 찾아가서 그곳의 값(10)을 읽어와 10을 더한 후 20으로 덮어쓴다
함수 종료: printf("a: %d\n", a) - add_ten 함수가 종료되고 main으로 돌아와 변경된 a의 값 20을 출력한
#include <stdio.h>
void add_ten(int* pa);
int main()
{
int a = 10;
add_ten(&a);
printf("a : %d\\n", a);
return 0;
}
void add_ten(int* pa) { // pa가 변수, *는 그 주소의 값을 가리킴, 정수형 포인터 전체를 엑세스 할 수 있음
*pa = *pa+10;
}
스택 메모리와 포인터의 생명주기
function1과 function1_1: 이 함수들은 매개변수 a의 주소를 반환하는데, 생명주기가 함수가 종료되면 a가 할당된 스택 메모리 영역도 자동으로 해제되고 다른 용도로 재사용 될 수 있다. 따라서 main 함수로 반환된 주소는 더 이상 유효하지 않은 댕글링 포인터가 된다. 또 이 주소를 사용하여 값을 읽거나 쓰면 예상치 못한 동작이나 프로그램 충돌이 발생한다.
function2와 function2_1 은 인수로 받은 포인터 pa가 가리키는 외부 메모리 주소를 그대로 반환한다. pa 자체는 function2의 지역변수이지만, pa가 저장하고 있는 값(main 함수에 있는 arr 주소)은 main 함수가 종료되기 전까지 유효하다. 따라서 pb = function2(arr) 실행 후 포인터 pb는 main 함수의 지역배열 arr의 첫번째 요소의 주소를 안전하게 가리킨다
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
//p: 0000003DDCBDF880
//u : 3703437440
char* function1(char a) {
return &a; // 복사된 a의 주소값, return 타입이 포인터가 될 수 있다
}
int* function1_1(char a) {
return &a; // 복사된 a의 주소값, return 타입이 포인터가 될 수 있다
}
//int* function2(int* pa) {
// return &pa; // 지역 변수 pa 자체가 스택에서 차지하고 있는 주소(예: 0x3000)를 반환
//}
int* function2(int* pa) {
return pa; // *pa가 아닌 pa (전달받은 주소 자체)를 반환
}
int* function2_1(int* pa) {
return pa; // *pa가 아닌 pa (전달받은 주소 자체)를 반환
}
int main() {
char a = 'Y';
printf("1 p: %p\\n", function1(a));
printf("1 u: %u\\n", function1(a));
printf("1_1 p: %p\\n", function1_1(a));
printf("1_1 u: %u\\n", function1_1(a));
int arr[3] = { 1,2,3 };
//int arr2[3] = { 0, }; // 배열은 상수
//arr2 = function2(arr);
// (void*)로 형변환: %p는 일반적으로 void* 타입의 인수를 기대하므로, 주소 값인 &arr[i]를 (void*)로 명시적 형변환 해주는 것이 표준입니다.
for (int i = 0; i < 3; i++) {
printf("arr[%d] : 값 - %d, 주소값 - %p\\n", i, arr[i], (void*) & arr[i]); // arr[i]
}
int* pb = { 0, };
pb = function2(arr);
for (int i = 0; i < 3; i++) {
printf("arr[%d] : 값 - %d, 주소값 - %p\\n", i, pb[i], (void*) &pb[i]); // arr[i]
}
return 0;
}
function1과 function1_1이 안전한 주소를 반환하기 위해서 힙 메모리를 사용하거나 정적 메모리를 사용하면 된다고 한다.
1. 전역/외부 메모리 사용 (힙 메모리)
함수 내부에서 동적 할당 함수인 malloc을 사용하여 힙(Heap) 메모리에 데이터를 생성하고 그 주소를 반환합니다.
#include <stdlib.h> // malloc, free 사용
char* function_safe() {
// 힙 메모리에 char 1바이트를 할당하고 그 주소를 반환
char* new_data = (char*)malloc(sizeof(char));
if (new_data == NULL) return NULL;
*new_data = 'Z';
// 이 메모리는 function_safe가 종료되어도 해제되지 않고 남아있음.
return new_data;
}
int main() {
char* ptr = function_safe();
printf("안전한 접근: %c\n", *ptr);
// 힙 메모리는 반드시 사용 후 명시적으로 해제해야 함
free(ptr);
return 0;
}
- 장점: 함수 종료와 무관하게 데이터가 유지됩니다.
- 단점: 사용자가 반드시 **free()**를 호출하여 메모리를 해제해줘야 합니다 (메모리 누수 위험).
2. 정적 메모리 사용 (데이터 영역)
변수를 static으로 선언하여 스택이 아닌 데이터 영역에 할당되도록 합니다. 데이터 영역에 할당된 변수는 프로그램 시작부터 종료까지 유지됩니다.
char* function_static_safe() {
// static 변수는 함수가 종료되어도 메모리가 유지됨
static char static_a = 'S';
return &static_a;
}
int main() {
char* ptr = function_static_safe();
printf("안전한 접근 (Static): %c\n", *ptr);
// 이 메모리는 프로그램 종료 시 자동 해제되므로 free()가 필요 없음.
return 0;
}
- 장점: free()가 필요 없으며 안전합니다.
- 단점: static 변수는 프로그램 전체에서 단 하나의 인스턴스만 존재하므로, 이 함수를 여러 번 호출할 경우 모든 호출이 같은 메모리 공간을 공유하게 되어 문제가 발생할 수 있습니다.
2차원 배열
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main() {
int arr[3][3] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
printf("%d\\t", arr[i][j]);
}
printf("\\n");
}
return 0;
}
동적 메모리 할당을 사용하여 2차원 배열 구현 후 정적 2차원 배열과 비교하기
| 특징 | 동적 배열 (arr) | 정적 배열 (arr2) |
| 할당 위치 | 힙(Heap) 메모리 | 스택(Stack) 메모리 (지역 변수 기준) |
| 크기 결정 | **런타임(실행 중)**에 결정 가능 | 컴파일 시점에 고정 |
| 메모리 관리 | 사용 후 **반드시 free()**로 명시적 해제 필요 | 함수 종료 시 자동 해제됨 |
| 유연성 | 크기 조절 용이, 큰 메모리 할당 가능 | 크기 고정, 스택 크기 제한으로 큰 배열 할당 어려움 |
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h> // malloc, free를 사용하기 위해 필수
#define SIZE1 3
#define SIZE2 3
int main() {
// arr 은 int* 타입의 주소를 저장할 수 있는 공간(즉, 행 주소를 저장할 배열)을 SIZE1개 만큼 힙(Heap) 메모리에 할당한다 (int* 주소 3개를 저장할 공간 확보)
int** arr = (int**)malloc(sizeof(int*) * SIZE1);
// malloc 실패 시 처리 로직 추가 권장
if (arr == NULL) {
return 1; // 메모리 할당 실패 시 종료
}
for (int i = 0; i < SIZE1; i++) {
// 각 행의 주소 arr[i] 마다 실제 데이터를 저장할 공간을 다시 힙 메모리에 할당
arr[i] = (int*)malloc(sizeof(int) * SIZE1);
if (arr[i] == NULL) {
return 1;
}
for (int j = 0; j < SIZE1; j++) {
arr[i][j] = (i * SIZE1) + (j + 1);
printf("%d\\t", arr[i][j]);
}
printf("\\n");
}
// 동적 할당된 메모리는 사용 후 반드시 해제해야 함. 안그러면 메모리 누수 발생
for (int i = 0; i < SIZE1; i++) {
free(arr[i]); // 각 행의 메모리 해제
}
free(arr); // 행 포인터 배열의 메모리 해제
// 정적 2차원 배열 생성 - arr2는 함수 내부의 지역변수이므로 스택 메모리에 할당된다
// 배열의 크기는 컴파일 시점에 고정되고 사용 후 free해줄 필요 없이 함수 종료시 자동으로 해제된다
int arr2[SIZE2][SIZE2] = { 0, };
int cnt = 1;
printf("\\n\\n");
for (int m = 0; m < SIZE2; m++) {
for (int n = 0; n < SIZE2; n++) {
arr2[m][n] = cnt++;
printf("%d\\t", arr2[m][n]);
}
printf("\\n");
}
return 0;
}
학생의 점수 구하기 - 이중 포인터
sizeof(int*) * row: 행의 주솔르 저장할 공간 할당
sizeof(int) * col: 실제 점수 데이터를 저장할 공간 할당
call by value: input_score(score, row, col) 호출 시 score 포인터의 값(힙 메모리 시작 주소)이 복사되어 함수로 전달된다. 함수는 이 주소를 통해 원본 힙 메모리 공간에 접근하여 데이터를 저장하고 읽을 수 있다
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h> // malloc, free를 사용하기 위해 필수
// 동적 2차원 배열을 구성하는 함수
void input_score(int** arr, int row, int col) {
printf("--- 점수 입력 시작 ---\n");
for (int i = 0; i < row; i++) {
arr[i] = (int*)malloc(sizeof(int) * col);
printf("%d번째 학생의 4과목 점수 입력 (예: 90 85 70 95): ", i + 1);
for (int j = 0; j < col; j++) {
scanf("%d", &arr[i][j]);
}
}
printf("--- 점수 입력 완료 ---\n\n");
}
// 점수를 출력하고 총점 및 평균을 계산하는 함수
void output_score(int **arr, int row, int col) {
double avg;
printf("--- 점수 계산 결과 ---\n");
for (int i = 0; i < row; i++) {
int total = 0;
for (int j = 0; j < col; j++) {
total += arr[i][j];
}
avg = total / 4.0;
printf("%d번째 학생의 총점: %d, 평균: %.2lf\n", i + 1, total, avg);
}
printf("----------------------\n");
}
int main() {
int row = 3, col = 4; // 3명의 학생, 4과목
// 학생 수(row)만큼 int* (포인터) 배열을 힙에 할당
int** score = (int**)malloc(sizeof(int*) * row);
input_score(score, row, col);
output_score(score, row, col);
for (int i = 0; i < row; i++) {
if (score[i] != NULL) { // 각 행의 메모리가 할당되었는지 확인 후 해제
free(score[i]);
}
}
free(score); // 포인터 배열 자체 해제
return 0;
}
동물 입력받기 - 정적 2차원 char 배열
배열의 크기: sizeof(animal) / sizeof(animal[0])
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
// cha arrr[][20], char arr[5][20]
void input_animal(char (*arr)[20], int count) {
for (int i = 0; i < count; i++) {
scanf("%s", arr[i]); // arr[i]: 이 자체가 포인터이다
}
}
int main() {
char animal[5][20]; // 5개의 행과 각 행당 최대 20개의 문자를 저장할 수 있는 공간을 스택 메모리에 할당
// char ** pa;
// pa = animal; // 에러남: 2차원 배열의 이름(animal)은 첫 번째 행 &animal[0]을 가리키는 포인터인데, 이는 char (*)[20] 타입이다. 이를 char** 타입의 포인터에 대입하면 경고가 발생한다.
// char* pb;
// pb = animal[1]; // 단일 포인터 - 디버그 찍어보면 주소가 보인다
int count;
count = sizeof(animal) / sizeof(animal[0]); // 배열 크기 계산
input_animal(animal, count);
for (int i = 0; i < count; i++) {
printf("%d번째 동물 : %s\\n", i+1, animal[i]);
}
return 0;
}
3차원 배열
삼중 반복문을 사용하여 3차원 배열의 모든 요소를 논리적인 순서대로 접근한다
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h> // malloc, free를 사용하기 위해 필수
// cha arrr[][20], char arr[5][20]
void input_animal(char (*arr)[20], int count) {
for (int i = 0; i < count; i++) {
scanf("%s", arr[i]); // animal[i]: 포인터이다
}
}
int main() {
int score[2][3][4] = {
{{72, 80, 95, 60}, {68, 98, 83, 90}, {75, 72, 84, 90}},
{{66, 85, 90, 88}, {95, 92, 88, 95}, {43, 72, 56, 75}},
};
for (int i = 0; i < 2; i++) {
printf("%d반 점수\\n", i+1);
for (int j = 0; j < 3; j++) {
for (int k = 0; k < 4; k++) {
printf("%5d", score[i][j][k]);
}
printf("\\n");
}
printf("\\n");
}
return 0;
}

문자열 상수의 주소를 저장하는 포인터 배열을 사용
pary는 char* 타입의 포인터 변수 5개를 요소로 가지는 1차원 배열이다
pary[0]부터 pary[4]는 메모리 주소를 저장할 수 있으며 이 주소는 char 타입의 데이터(문자열의 첫글자)를 가리킨다
"dog" 와 같은 문자열 상수는 프로그램이 실행될 때 읽기전용 데이터 영역에 저장된다. 컴파일러는 각 문자열 상수를 메모리에 배치하고 그 문자열의 시작주소를 자동으로 결정한다. 대입 연산자(=)는 문자열 자체('d', 'o', 'g', '\0')를 복사하는 것이 아니라 해당 문자열이 저장된 메모리의 시작주소를 pary[0]에 저장한다
printf 함수의 %s 형식 지정자는 문자열의 시작주소(포인터)를 인수로 기대한다. pary[i]는 저장하고 있는 문자열의 시작주소를 printf에 전달한다. printf는 그 주소로부터 메모리를 한 문자씩 읽어나가다가 널 종료 문자 \0를 만날때까지 문자를 출력한다.
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
int main() {
char* pary[5]; // 모던 c - 캐릭터 포인터형, 주소를 값으로 가지는 1차원 배열 , 고전 c- 2중 포인터(char *pary[5])
pary[0] = "dog"; // 번지: 컴퓨터가 알아서 주소를 넣어줌
pary[1] = "elephant";
pary[2] = "horse";
pary[3] = "tiger";
pary[4] = "lion";
for (int i = 0; i < 5; i++) {
printf("%s\\n", pary[i]);
}
return 0;
}

문자열 메모리 공간 요약
| 저장소 | 특징 | 코드 예시 |
| 읽기 전용 데이터 영역 (힙/스택 외) | 문자열 상수가 저장되는 곳. 프로그램 시작 시 메모리가 할당되며, 수정 불가능 (수정 시 런타임 오류 위험). | "dog" |
| 스택 메모리 | 포인터 배열 pary 자체가 저장되는 곳. 각 요소(pary[i])는 주소 값만 저장함. | char* pary[5] |
주소 연결 관계와 역참조 연산(*) 코드
a: a의 값이 나온다
&a: a의 주소값이 나온다
pi: pi의 값이 나온다 (pi의 값은 &a, 즉 a의 주소값이다)
&pi: pi의 주소값이 나온다
*pi: pi의 값이 나온다
ppi: ppi의 값이 나온다 (ppi의 값은 &pi, 즉 pi의 주소 값이다)
&ppi: ppi의 주소값이 나온다
*ppi: ppi 가 가리키는 값이 나온다(pi의 값이 나오는데, pi의 값은 a의 주소)
**ppi: ppi가 가리키는 값의 값이 나온다(pi가 가리키는 a의 값)
#include <stdio.h>
int main() {
// ----------------------------------------------------
// [1] 변수 선언: 포인터 레벨이 다르면 값 교환/대입이 안됨을 이해하는 예제
// ----------------------------------------------------
int a = 10; // 일반 정수형 변수 (int)
int* pi; // pi: 정수(int)의 주소를 저장하는 포인터 변수 (int*)
int** ppi; // ppi: 포인터(int*)의 주소를 저장하는 이중 포인터 변수 (int**)
// ----------------------------------------------------
// [2] 값 할당 및 연결 (메모리 주소 연결)
// ----------------------------------------------------
pi = &a; // pi에 'a'의 주소(&a)를 저장 -> pi는 a를 가리킴
ppi = π // ppi에 'pi'의 주소(&pi)를 저장 -> ppi는 pi를 가리킴
// ----------------------------------------------------
// [3] 값 출력
// ----------------------------------------------------
printf("------------------------------------\\n");
printf("변수\\t 변숫값\\t &연산\\t *연산\\t **연산\\n");
printf("------------------------------------\\n");
// a 변수: 일반 값과 주소(&) 출력
// &a는 주소이므로 %u (unsigned int)로 출력 (정확히는 %p를 써야 함)
printf(" a%10d%10u\\n", a, &a);
// pi 포인터: pi에 저장된 값(a의 주소), pi 자신의 주소(&pi), pi가 가리키는 값(*pi) 출력
// *pi는 a의 값이므로 %d (int)로 출력
printf(" pi%10u%10u%10d\\n", pi, &pi, *pi);
// ppi 이중 포인터: ppi의 값(pi의 주소), ppi 자신의 주소(&ppi), *ppi, **ppi 출력
// *ppi는 pi의 값이므로 %u (pi의 값 = a의 주소)로 출력
// **ppi는 a의 값이므로 %u (a의 값)로 출력
printf("ppi%10u%10u%10u%10u\\n", ppi, &ppi, *ppi, **ppi);
return 0;
}
이중포인터
call by reference (참조에 의한 호출)
**ppa 와 **ppb는 문자열의 맨 첫번째 글자와 같다
#include <stdio.h>
void swap(char** ppa, char** ppb);
int main() {
char* pa = "success";
char* pb = "failure";
printf("before - pa: %s, pb: %s\\n", pa, pb);
swap(&pa, &pb);
printf("after - pa: %s, pb: %s\\n", pa, pb);
return 0;
}
void swap(char** ppa, char** ppb) {
printf("ppa : %c, ppb : %c\\n", **ppa, **ppb);
char* temp = *ppa;
*ppa = *ppb;
*ppb = temp;
}
이중포인터2
읽기전용 데이터 영역에 있는 문자열 상수(data[])를 힙 메모리로 옮겨 복사한다
malloc(sizeof(char*) * count)는 4개의 포인터 주소를 저장할 공간을 힙에 확보한다.
ptr_ary[i] = (char*)malloc(...) 으로 각 포인터 요소(ptr_ary[i])마다 문자열 길이 + 널 문자 만큼의 공간을 새로 힙에 할당한다
strcpy로 data[i]가 가리키는 원본 문자열을 새로 할당된 힙 공간 ptr_ary[i]로 복사한다
ptr_ary[i] 는 힙 메모리에 있으므로 나중에 프로그램 내에서 내용을 자유롭게 수정할 수 있다 (문자열 상수는 수정할 수 없다)
malloc을 통해 힙에 할당된 모든 메모리는 반드시 free를 통해 해제해야 한다 (각 문자열 데이터 free(ptr_ary[i])를 한 후 포인터 배열 자체 free(ptr_ary)를 해제해야 한다)
char* ptr_ary[] 방식은 문자열을 동적 할당하지 않아 메모리 해제가 필요없고 빠르지만, 문자열의 내용을 수정할 수 없다
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void print_str(char** pps, int cnt);
int main() {
//char ptr_ary1[4][10]; // (X) 문자열 배열 상수
//char* ptr_ary[4] = { "eagle", "tiger", "lion", "squirrel" };
// 첫 번째 방법
//char* ptr_ary[] = { "eagle", "tiger", "lion", "squirrel" };
//char** ptr_ary3 = (char*[]){ "eagle", "tiger", "lion", "squirrel" }; // 강제 캐스팅
//int count = sizeof(ptr_ary) / sizeof(ptr_ary[0]);
//print_str(ptr_ary, count);
// 두 번째 방법
const char* data[] = { "eagle", "tiger", "lion", "squirrel" };
int count = sizeof(data) / sizeof(data[0]);
char** ptr_ary = (char**)malloc(sizeof(char*) * count);
for (int i = 0; i < count; i++) {
int len = strlen(data[i]) + 1;
ptr_ary[i] = (char*)malloc(sizeof(char) * len);
strcpy(ptr_ary[i], data[i]);
}
print_str(ptr_ary, count);
for (int i = 0; i < count; i++) {
free(ptr_ary[i]);
}
return 0;
}
void print_str(char** pps, int cnt) {
for (int i = 0; i < cnt; i++) {
printf("%s\\n", pps[i]);
}
}
문제
(함수) 두 정수를 입력받아서 사칙연산 하는 함수 만들기
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
// 함수가 2개 이상이면 헤더 만들기
int plus(int a, int b);
int minus(int a, int b);
int multiple(int a, int b);
double divide(int a, int b);
int main()
{
int a, b;
printf("정수를 입력해주세요: ");
scanf("%d %d", &a, &b);
printf("%d + %d = %d\\n", a, b, plus(a, b));
printf("%d - %d = %d\\n", a, b, minus(a, b));
printf("%d * %d = %d\\n", a, b, multiple(a, b));
printf("%d / %d = %.2lf\\n", a, b, divide(a, b));
return 0;
}
int plus(int a, int b) {
return a + b;
}
int minus(int a, int b) {
return a - b;
}
int multiple(int a, int b) {
return a * b;
}
double divide(int a, int b) {
return (double) a / b;
}
(포인터) 두 수를 교환하는 함수 만들기
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
// 스택(64kb ~ 1.2mb), 주소 전달, 확장된 것: 힙 (객체지향)
/*
정확한 설명 (Call by Reference)
Swap 함수의 메커니즘을 정확하게 정리하면 다음과 같습니다.
주소 전달 (& 사용): main 함수에서 Swap(&a, &b)를 호출할 때, & 연산자를 사용하여 변수 a와 b가 **메모리에서 실제 저장된 위치(주소)**를 Swap 함수로 전달합니다.
포인터 수신 (* 정의): int Swap(int* a, int* b)에서 매개변수 a와 b는 포인터 변수로, 전달받은 주소값을 저장합니다.
값 변경 (* 사용): 함수 내부에서 *a와 *b에 붙은 *는 역참조 연산자로, **"이 주소가 가리키는 곳에 가서 값을 읽거나 써라"**라는 의미를 가집니다.
결과: Swap 함수는 주소를 통해 main 함수에 있는 원본 변수 a와 b의 메모리 공간에 직접 접근하여 값을 서로 교환합니다. 이것이 함수 호출 후에도 main 함수의 변수 값이 바뀌는 이유입니다.
*/
int Swap(int* a, int* b);
int main() {
int a, b;
scanf("%d %d", &a, &b);
Swap(&a, &b);
printf("%d %d", a, b);
}
int Swap(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}
wsl 설치하기
------------------------
1. powershell
------------------------
wsl -l -o
wsl --install
wsl --install -d Ubuntu-24.04
------------------------
2. 다시시작 후 계정 설정
계정
계정명 / 계정비번
------------------------
wsl --version
wsl -l -v
wsl -d Ubuntu
------------------------
3. wsl 안으로 들어감
------------------------
cd
pwd
sudo apt update
이미 우분투가 설치가 되어있을 때
