[W04] 퀴즈 정리

silver ·6일 전

크래프톤 정글

목록 보기
15/19

1. 다음 C 코드의 문제점을 찾아 해결하고, printf 출력 결과를 적으시오.

#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해야 한다는 점을 기억하기.

2. 다음 코드를 실행했을 때 printf 출력 값이 2 3 4 5 6 이 되도록 func 함수를 완성하시오.

#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] 를 수정하면 원본 배열의 값도 수정된다.

3. C언어로 정수 배열 arr에 저장된 원소들의 순서를 거꾸로 뒤집어 출력하는 프로그램을 작성하시오 .

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

temp 배열을 사용하는 경우

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

* 반복문 조건을 작성한 다음에는 처음 실행될 때 조건이 참인지 한 번 확인하기.

4. C언어 코드의 실행 결과를 기입 하시오.

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 자신의 주소

라고 생각하면 됨.

5. printf('a');

위 코드는 알파벳 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을 문자열 주소로 오인해 그 위치로 접근을 시도하면서 정의되지 않은 동작이 발생하고, 대부분 세그멘테이션 폴트로 프로그램이 강제 종료된다.

6. 다음은 링크드리스트에서 특정 위치의 Node를 찾는 코드이다. 구조체의 내용을 참고하여. 빈 칸의 코드를 작성하시오.

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의 주소로 이동시키는 것.

0개의 댓글