#include <stdio.h>
#include <stdlib.h>
int main() {
int *arr = (int *)malloc(5 * sizeof(int));
for (int i = 0; i < 5; i++) {
arr[i] = i * i;
}
printf("%d\n", arr[3]); // 결과 기입
// start of code
...
// end of code
return 0;
}
원래 코드의
int *arr = (int *)malloc(5 * sizeof(int));
는 int 5개를 저장할 수 있는 공간을 동적으로 할당하는 코드이다.
malloc()은 할당한 메모리의 주소를 반환하기 때문에 포인터로 받아야 한다.
int *arr = malloc(5 * sizeof(int));
반면, 내가 처음에 작성했던 int len = malloc(sizeof(int)); 은 malloc()이 반환하는 주소를 int형 변수에 저장하려고 하기 때문에 잘못된 코드이다.
그리고 동적으로 할당한 메모리는 사용이 끝난 후 free()를 사용해서 해제해야 한다.
int *arr = (int *)malloc(5 * sizeof(int));
for (int i = 0; i < 5; i++) {
arr[i] = i * i;
}
printf("%d\n", arr[3]);
free(arr);
9
malloc() → 필요한 크기의 메모리를 동적으로 할당
int *arr = malloc(5 * sizeof(int));
→ int 5개를 저장할 공간을 만들고 시작 주소를 arr에 저장
사용이 끝나면 free(arr);
→ 동적으로 할당한 메모리를 해제
malloc → 메모리 할당
↓
사용
↓
free → 메모리 해제
* 동적 할당에서는 주소를 반환한다는 점과 사용 후 free해야 한다는 점을 기억하기.
#include <stdio.h>
void func(/*....*/) {
// ....
}
int main() {
int arr[5] = {1, 2, 3, 4, 5};
func(arr, 5);
for (int i = 0; i < 5; i++) {
printf("%d ", arr[i]);
}
return 0;
}
void func(int arr, int len) {
for (int i = 0; i < len; i++) {
arr[i] = arr[i] + 1;
}
}
배열을 함수에 전달할 때는 배열의 시작 주소가 전달된다.
따라서 함수에서는 배열을 가리키는 포인터로 받아야 한다.
→ void func(int *arr, int len)
또는 void func(int arr[], int len) 으로 작성할 수 있다.
내가 작성한 코드에서는 int arr 이라고 작성했는데, int 하나를 받는 변수이기 때문에 arr[i] 처럼 배열 형태로 사용할 수 없다.
void func(int *arr, int len) {
for (int i = 0; i < len; i++) {
arr[i] = arr[i] + 1;
}
}
실행하면 1 2 3 4 5 가 함수에 전달되고,
각 원소 + 1
↓
2 3 4 5 6
이 된다.
배열을 함수에 전달할 때 func(arr, 5); 에서 arr은 배열의 첫 번째 원소를 가리키는 주소처럼 전달된다.
arr
↓
[1][2][3][4][5]
↑
시작 주소
따라서 함수에서는 int *arr로 받을 수 있다.
* 배열을 함수에서 수정하려면 배열의 주소를 전달받아야 한다.
void func(int *arr, int len)
그리고 arr[i] 를 수정하면 원본 배열의 값도 수정된다.
int main(void) {
int arr[5] = {1, 2, 3, 4, 5};
// 코드 작성
printf("\n");
return 0;
}
int temp[5] = {};
for (int i = 4; i < 0; i--) {
temp[i] = arr[i - 4];
}
for (int j = 0; j < 5; j++) {
printf("%d", temp[j]);
}
가장 큰 문제는 반복문의 조건이다.
for (int i = 4; i < 0; i--)
처음에 i = 4 이므로 4 < 0 이 거짓이다.
따라서 반복문이 아예 실행되지 않는다.
역순으로 감소시키려면 i >= 0 이어야 한다.
그리고 굳이 temp 배열을 만들지 않아도 역순으로 출력할 수 있다.
for (int i = 4; i >= 0; i--) {
printf("%d ", arr[i]);
}
5 4 3 2 1
int temp[5];
for (int i = 0; i < 5; i++) {
temp[i] = arr[4 - i];
}
for (int i = 0; i < 5; i++) {
printf("%d ", temp[i]);
}
arr
[1][2][3][4][5]
↓ ↓ ↓ ↓ ↓
temp
[5][4][3][2][1]
배열의 인덱스는 0 1 2 3 4 이므로 마지막 원소는 arr[4].
역순으로 접근하려면
arr[4]
arr[3]
arr[2]
arr[1]
arr[0]
처럼 접근하면 된다.
역순 반복문: for (int i = 4; i >= 0; i--)
i < 0이 아니라 i >= 0
* 반복문 조건을 작성한 다음에는 처음 실행될 때 조건이 참인지 한 번 확인하기.
void update(int p) { p = 200;}
void update1(int *p) { *p = 100;}
void update2(int **p) { **p = 1000;}
int main() {
int x = 10;
int *p = &x;
update(x); printf("%d\n", x);
update1(&x); printf("%d\n", x);
update2(&p); printf("%d\n", x);
update1(p); printf("%d\n", x);
}
10
100
1000
100
① update(x)
void update(int p) {
p = 200;
}
x의 값만 복사해서 전달한다.
x = 10
update(x)
↓
p = 10
p = 200
p를 200으로 바꿔도 원래 x는 그대로이다.
x = 10
② update1(&x)
void update1(int *p) {
*p = 100;
}
&x는 x의 주소이다.
x
┌────┐
│ 10 │
└────┘
↑
주소
p가 x의 주소를 가지고 있으므로
*p = 100;
하면 실제 x의 값이 변경된다.
x = 100
③ update2(&p)
여기서는 이중 포인터가 사용된다.
int *p = &x;
현재 구조는
p ─────→ x
100
&p는 p의 주소이므로
update2(&p);
에서 int **p로 받는다.
**p = 1000;
결국 x의 값을 1000으로 변경한다.
p ─────→ x
1000
④ update1(p)
현재
p → x
이므로
update1(p);
를 하면
*p = 100;
이 실행된다.
따라서 x는 다시
100
이 된다.
int p
→ 값 자체를 전달
int *p
→ 주소를 전달
→ *p로 실제 값에 접근
int **p
→ 포인터의 주소를 전달
→ **p로 포인터가 가리키는 실제 값에 접근
특히
p 는 주소
*p 는 그 주소에 저장된 값
&p 는 p 자신의 주소
라고 생각하면 됨.
위 코드는 알파벳 a를 출력하기 위해 작성된 것이다.
이 코드는 문제가 있을까? 문제가 없다면 “없다”고 기입하고,
문제가 있다면 그 이유를 서술하시오.
제출했던 답:
있다.
C언어에서는 변수를 지정할 때 데이터타입의 명시가 필요하다. 원하는 변수나 문자 등을 출력하기 위해서는 파이썬의 출력방식처럼 해당 변수나 문자형을 입력하는 것이 아니라, 출력하고자 하는 것의 데이터 타입에 맞는 형식을 갖춰서 출력해야 한다. 이 경우는 문자형을 출력하는 %c를 통해서 문자형을 출력해야 한다.
문제는 printf() 안에서 형식 지정자 없이 'a'를 넣었다는 것이다.
문자 하나를 출력할 때는 %c를 사용한다.
printf("%c", 'a');
또는
char ch = 'a';
printf("%c", ch);
'a' → 문자 하나
"a" → 문자열
따라서 문자 하나를 출력할 때는 %c, 문자열을 출력할 때는 %s 를 사용한다.
* printf()는 출력할 값의 자료형에 맞는 형식 지정자를 사용해야 한다.
%d → 정수
%f → 실수
%c → 문자
%s → 문자열
printf의 첫 번째 인자는 항상 const char *, 즉 문자열의 시작 주소를 받도록 설계되어 있다. "a"처럼 큰따옴표로 감싸면 컴파일러가 이를 문자열 리터럴로 인식해 메모리에 저장하고 그 주소를 넘기므로, 형식 지정자 없이도 그대로 출력된다.
반면 'a'는 문자열이 아니라 문자 상수이며, C에서는 이를 정수(int, 아스키 값 97)로 취급한다. printf('a')를 호출하면 char *가 와야 할 자리에 int가 들어오는 타입 불일치가 발생하는데, C는 이를 엄격히 막지 않기 때문에 보통 컴파일은 경고와 함께 통과된다. 문제는 실행 시점에 나타난다 — printf가 정수 97을 문자열 주소로 오인해 그 위치로 접근을 시도하면서 정의되지 않은 동작이 발생하고, 대부분 세그멘테이션 폴트로 프로그램이 강제 종료된다.
typedef struct _listnode{
int item;
struct _listnode *next;
} ListNode;
typedef struct _linkedlist{
int size;
ListNode *head;
} LinkedList;
ListNode * findNode(LinkedList *ll, int index){
ListNode *temp;
// 코드 작성
return temp;
}
제출한 답
ListNode *cur;
cur = ll->head;
cur->next = NULL;
while(cur != NULL){
temp->item = cur->item;
cur = cur->next;
}
이 문제는 index 번째 Node를 찾아서 그 Node의 주소를 반환하는 문제이다.
그런데 내가 작성한 코드에서는 index를 사용하지 않고 있다.
또한 cur->next = NULL; 은 현재 노드의 다음 노드를 NULL로 만들어서 연결리스트의 연결을 끊는 코드이다.
원래 A → B → C → D 였다면
A->next = NULL; 을 하면
A B → C → D
↓
NULL
처럼 연결이 끊긴다.
따라서 Node를 찾을 때는 next의 값을 변경하면 안 되고, 포인터를 다음 Node로 이동시켜야 한다.
ListNode *findNode(LinkedList *ll, int index) {
ListNode *temp = ll->head;
for (int i = 0; i < index; i++) {
temp = temp->next;
}
return temp;
}
예를 들어
head
↓
[10] → [20] → [30] → [40]
에서
findNode(ll, 2);
를 호출하면
처음
temp → [10]
i = 0
temp → [20]
i = 1
temp → [30]
따라서 index 2인 [30] Node를 반환한다.
링크드리스트에서 Node를 이동할 때는
temp = temp->next;
를 사용한다.
temp
↓
[10] → [20] → [30]
↑
temp->next
temp->next는 다음 Node의 주소이다.
그리고 temp->item 은 현재 Node에 저장된 값이다.
temp → 현재 Node의 주소
temp->item → 현재 Node의 값
temp->next → 다음 Node의 주소
링크드리스트에서 특정 위치의 Node를 찾는 기본적인 형태:
ListNode *temp = ll->head;
for (int i = 0; i < index; i++) {
temp = temp->next;
}
return temp;
* Node를 직접 움직이는 게 아니라 temp 포인터를 다음 Node의 주소로 이동시키는 것.