Post

[2024년] 정보처리기사 실기 - C 언어

2024년도 1회, 2회, 3회의 정보처리기사 실기 기출문제 속 C 언어 문제를 정리하였다.

2024년 1회 정보처리기사 실기

문제 1. 시프트 연산자 (», «)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
#include <stdlib.h>

main(int argc, char *argv[]) {
  int v1 = 0;
  int v2 = 35;
  int v3 = 29;

  if (v1 > v2 ? v2 : v1) {
    v2 = v2 << 2;
  } else {
    v3 = v3 << 2;
  }

  printf("%d", v2 + v3);
  return 0;
}
더보기

[정답]
151

[풀이]

1
2
3
4
5
if (v1 > v2 ? v2 : v1) {
  v2 = v2 << 2;
} else {
  v3 = v3 << 2;
}
  • v1 > v20 > 35false (0)
    • 삼항 연산 결과 ➔ v10

C언어에서 if(값)은 값이 0이면 false고, 0이 아니면 true이다.
여기서는 결과가 0이므로 false다. ➔ else문이 실행된다.

1
v3 = v3 << 2;
  • else문 실행
    • v3 = v3 << 2;
1
2
v2 = 35   (2진수) ⟹ 100011
v3 = 29   (2진수) ⟹ 11101

v3 « 2 (왼쪽 시프트 연산자)
왼쪽으로 2칸 이동

1
2
3
4
왼쪽으로 2칸 이동한 값

(2진수) ⟹ 1110100
(10진수로 변환) ⟹ 116

v3 = 116이 된다.

1
printf("%d", v2 + v3);

v2 + v3
= 35 + 116
= 151


시프트 연산자 : 비트를 이동시키는 연산자

종류의미형식설명
»오른쪽으로 이동5 » 25의 이진표현을 오른쪽으로 2칸 이동
«왼쪽으로 이동5 « 25의 이진표현을 왼쪽으로 2칸 이동


c언어에서 return 0;은 프로그램 종료

1
2
3
4
main(int argc, char *argv[]) {

  return 0; // 프로그램을 정상적으로 종료했음을 의미
}

문제 2. 포인터 기반 문자열 처리

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <stdio.h>
#include <string.h>

void reverse(char* str, int len) {
  char temp;
  char *p1 = str;
  char *p2 = str + len - 1;

  while(p1 < p2) {
    temp = *p1;
    *p1 = *p2;
    *p2 = temp;
    p1++;
    p2--;
  }
}

int main() {
  char str[100] = "ABCDEFGH";
  int len = strlen(str);

  reverse(str, len);

  for(int i = 1; i < len; i += 2) {
    printf("%c", str[i]);
  }

  return 0;
}
더보기

[정답]
GECA

[풀이]
strlen() 함수
C 표준 라이브러리 함수이다. 이 함수는 문자열의 길이(문자 개수)를 반환한다.
문자열 str에서 널 문자(\0)가 나올 때까지 문자를 하나씩 세어 개수를 반환한다.

1
2
char str[100] = "ABCDEFGH";
int len = strlen(str); // 8

reverse 함수

  • 문자열의 앞과 뒤를 바꿔서 뒤집는 함수이다.
  • p1은 문자열의 시작, p2는 문자열의 끝을 가리킨다.
1
2
3
reverse(str, len);
// str = "ABCDEFGH"
// len = 8
1
2
3
4
5
6
7
8
9
10
11
12
13
void reverse(char* str, int len) {
  char temp;
  char *p1 = str;
  char *p2 = str + len - 1;

  while(p1 < p2) {
    temp = *p1;
    *p1 = *p2;
    *p2 = temp;
    p1++;
    p2--;
  }
}

초기 상태 :

1
2
p1 -> A (index 0)
p2 -> H (index 7)

reverse() 실행 과정

1
2
3
4
5
6
7
8
temp -> A
p1 -> H
p2 -> A
서로 교환이 됨 : H B C D E F G A

포인터 이동
p1 -> B (index 1)
p2 -> G (index 6)
단계p1 위치 (문자)p2 위치 (문자)교환 후 문자열 상태
초기A (index 0)H (index 7)A B C D E F G H
1A ↔︎ H H B C D E F G A
2B ↔︎ G H G C D E F B A
3C ↔︎ F H G F D E C B A
4D ↔︎ E H G F E D C B A
종료p1 ≥ p2 반복 종료

결과 문자열 : HGFEDCBA


1
2
3
for(int i = 1; i < len; i += 2) {
  printf("%c", str[i]);
}

인덱스 1부터 시작하여 2씩 증가하며 출력한다.
(홀수 인덱스의 문자만 출력한다.)

1
2
3
4
i = 1, str[1] = G
i = 3, str[3] = E
i = 5, str[5] = C
i = 7, str[7] = A

따라서 최종 출력 결과는 GECA이다.

문제 3. 문자 판별 함수

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
#include <ctype.h>

int main() {
  char *p = "It is 8";
  char result[100];
  int i;

  for (i = 0; p[i] != '\0'; i++) {
    if (isupper(p[i]))
      result[i] = (p[i] - 'A' + 5) % 25 + 'A';
    else if (islower(p[i]))
      result[i] = (p[i] - 'a' + 10) % 26 + 'a';
    else if (isdigit(p[i]))
      result[i] = (p[i] - '0' + 3) % 10 + '0';
    else if (!(isupper(p[i]) || islower(p[i]) || isdigit(p[i])))
      result[i] = p[i];
  }

  result[i] = '\0';
  printf("%s\n", result);

  return 0;
}
더보기

[정답]
Nd sc 1

[풀이]
isupper, islower, isdigit 함수
<ctype.h> 헤더 파일에 포함된 문자 판별 함수이다.
이 함수들은 각각 문자가 대문자, 소문자, 숫자인지를 판별하며, 결과로 참이면 0이 아닌 정수, 거짓이면 0을 반환한다.
주로 조건문에서 if (isupper(ch))처럼 사용된다.

  • isupper(c) : c가 대문자 알파벳(A~Z)인지 확인
  • islower(c) : c가 소문자 알파벳(a~z)인지 확인
  • isdigit(c) : c가 숫자 문자(0~9)인지 확인
    • 숫자 자체가 아니라, 문자 형태의 숫자여야 하며, 작은 따옴표로 감싸야 한다.

1
2
3
4
5
6
7
8
9
10
for (i = 0; p[i] != '\0'; i++) {
  if (isupper(p[i]))
    result[i] = (p[i] - 'A' + 5) % 25 + 'A';
  else if (islower(p[i]))
    result[i] = (p[i] - 'a' + 10) % 26 + 'a';
  else if (isdigit(p[i]))
    result[i] = (p[i] - '0' + 3) % 10 + '0';
  else if (!(isupper(p[i]) || islower(p[i]) || isdigit(p[i])))
    result[i] = p[i];
}

반복문에서는 문자열 "It is 8"의 각 문자를 하나씩 검사하면서 변환한다.

1. 대문자 처리 (isupper)

1
result[i] = (p[i] - 'A' + 5) % 25 + 'A';
  • 'A'를 기준으로 0 ~ 25 범위의 숫자로 변환
  • 여기에 5를 더해서 오른쪽으로 5칸 이동
  • %25로 범위를 제한
  • 다시 'A'를 더해서 문자로 복원

2. 소문자 처리 (islower)

1
result[i] = (p[i] - 'a' + 10) % 26 + 'a';
  • 'a' 기준으로 변환 후
  • 10칸 이동
  • %26으로 알파벳 범위 유지

3. 숫자 처리 (isdigit)

1
result[i] = (p[i] - '0' + 3) % 10 + '0';
  • 숫자를 정수로 변환 후
  • 3 더하기
  • %10으로 한 자리 유지

4. 기타 문자 처리

1
result[i] = p[i];


변환 과정

1
"It is 8"
문자종류변환 결과
I대문자N
t소문자d
(공백)기타(공백)
i소문자s
s소문자c
(공백)기타(공백)
8숫자1
1
printf("%s\n", result);

최종적으로 Nd sc 1이 출력된다.

문제 4. 구조체와 포인터를 이용한 함수 처리

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <stdio.h>

struct BankAcc {
  int accNum;
  double bal;
};

double sim_pow(double base, int year) {
  double r = 1.0;
  for (int i = 0; i < year; i++) {
    r = r * (1.0 + base);
  }
  return r;
}

void initAcc(struct BankAcc *acc, int x, double y) {
  acc->accNum = x;
  acc->bal = y;
}

void test01(struct BankAcc *acc, double en) {
  if (en > 0 && en < acc->bal) {
    acc->bal -= en;
  } else {
    acc->bal += en;
  }
}

void test02(struct BankAcc *acc) {
  acc->bal = acc->bal * sim_pow(0.1, 3);
}

int main() {
  struct BankAcc myAcc;
  initAcc(&myAcc, 9981, 2200.0);
  test01(&myAcc, 100.0);
  test02(&myAcc);
  printf("%d and %0.2f", myAcc.accNum, myAcc.bal);

  return 0;
}
더보기

[정답]
9981 and 2795.10

[풀이]
struct BankAcc myAcc;

1
2
3
4
struct BankAcc {
  int accNum;
  double bal;
};

myAcc라는 구조체 변수를 선언한다.
은행 계좌 정보를 저장하는 구조체 정의(BankAcc)후, 두 개의 멤버 변수를 포함한다.

  • accNum (계좌번호)
  • bal (잔액)


1
initAcc(&myAcc, 9981, 2200.0); // myAcc의 주소를 넘겨서 초기화 실행
1
2
3
4
void initAcc(struct BankAcc *acc, int x, double y) {
  acc->accNum = x;
  acc->bal = y;
}

accstruct BankAcc 타입의 구조체 변수를 가리키는 포인터이다.

  • myAcc.accNum = 9981
  • myAcc.bal = 2200.0


1
test01(&myAcc, 100.0);
1
2
3
4
5
6
7
8
9
10
11
12
// 입출금 처리 함수
/*
en이 양수이고 현재 잔액보다 작으면 -> 출금
현재 잔액보다 많거나, 음수이거나, 0 포함 등 그 외의 경우 -> 입금
*/
void test01(struct BankAcc *acc, double en) {
  if (en > 0 && en < acc->bal) {
    acc->bal -= en; // 출금을 의미
  } else {
    acc->bal += en; // 입금을 의미
  }
}
  • en > 0 && en < acc->bal ➔ 참
    • acc->bal -= en; 실행
    • myAcc.bal -= 100.0
    • 2200.0 - 100.0 = 2100.0


1
test02(&myAcc);
1
2
3
4
5
6
7
8
9
10
11
void test02(struct BankAcc *acc) {
  acc->bal = acc->bal * sim_pow(0.1, 3);
}

double sim_pow(double base, int year) {
  double r = 1.0;
  for (int i = 0; i < year; i++) {
    r = r * (1.0 + base);
  }
  return r;
}
  • 현재 acc->bal 값은 2100.0
  • sim_pow(0.1, 3)1.331

    ir 초기값연산 (r = r * (1.0 + base))연산 후 r 값
    01.01.0 * (1.0 + 0.1) = 1.11.1
    11.11.1 * (1.0 + 0.1) = 1.211.21
    21.211.21 * (1.0 + 0.1) = 1.3311.331
  • acc->bal = acc->bal * sim_pow(0.1, 3)
    = 2100.0 * 1.331
    = 2795.1


1
printf("%d and %0.2f", myAcc.accNum, myAcc.bal);
  • 형식 지정
    • %d : 정수형 (int) 출력
    • %.2f : 소수점 둘째 자리까지 실수형(double) 출력 ➔ 반올림
  • myAcc.accNum9981
  • myAcc.bal2795.1
  • 출력값 : 9981 and 2795.10

따라서 최종 출력은 9981 and 2795.10이 된다.


포인터와 구조체

  • 구조체를 함수의 매개변수로 전달할 경우
    • 값이 아닌 주소(포인터)를 전달하면 함수 내부에서 구조체 값을 직접 수정할 수 있다.
    • 구조체를 값으로 전달하면 원본은 변경되지 않지만, 포인터로 전달하면 원본 데이터가 변경된다.
    • struct BankAcc *acc는 구조체를 가리키는 포인터이며, acc->bal과 같은 방식으로 멤버에 접근한다.
    • 구조체 변수는 . 연산자를, 구조체 포인터는 -> 연산자를 사용한다.
      • (예: myAcc.bal, acc->bal)

2024년 2회 정보처리기사 실기

문제 5. 값에 의한 전달(Call by Value)과 switch문

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stdio.h>

void swap(int a, int b) {
  int t = a;
  a = b;
  b = t;
}

int main() {
  int a = 11;
  int b = 19;
  swap(a, b);

  switch(a) {
    case 1:
      b += 1;
    case 11:
      b += 2;
    default:
      b += 3;
      break;
  }

  printf("%d", a-b);
}
더보기

[정답]
-13

[풀이]

1
2
3
int a = 11;
int b = 19;
swap(a, b);
1
2
3
4
5
void swap(int a, int b) {
  int t = a;
  a = b;
  b = t;
}

swap() 함수 안에서만 ab값이 바뀌고, main()함수의 ab에는 아무 영향이 없다.
이는 C언어가 값에 의한 전달(Call by Value) 방식을 사용하기 때문이다.

즉, swap(a, b)를 호출할 때, main의 a, b 값이 복사되어 함수의 매개변수 a, b에 전달된다.

swap() 함수에서 사용되는 변수 a, b, t는 모두 해당 함수 내부에서 선언된 지역 변수이다.

1
2
3
4
// swap() 함수 내에서의 변수 변화
t = 11
a = 19
b = 11

하지만 이 변화는 복사된 값에만 적용되므로
main 함수의 a = 11, b = 19는 그대로 유지된다.

[지역 변수]

  • 함수가 실행될 때 생성되고, 함수가 종료되면 메모리에서 사라진다.
  • 다른 함수의 변수와는 완전히 독립적인 메모리 공간을 사용한다.
  • 따라서 swap()에서의 변경은 main에 영향을 주지 않는다.


1
2
3
4
5
6
7
8
9
switch(a) {
  case 1:
    b += 1;
  case 11:
    b += 2;
  default:
    b += 3;
    break;
}

현재 a 값은 11이다.
따라서 case 11: 부터 실행된다.

  • b += 2
    = 19 + 2
    = 21

case 11: 뒤에 break가 없기 때문에
아래의 default까지 계속 실행되는 fall-through가 발생한다.

default 부분이 실행된다.

  • b += 3
    = 21 + 3
    = 24

default에는 break문이 있어서 switch문이 종료된다.
최종적으로 b값은 24이다.


1
printf("%d", a-b);

현재 a의 값은 11, b의 값은 24

11 - 24 = -13
최종 출력값은 -13이다.


🤔 만약 swap이 실제로 적용되려면?

주소를 전달하는 방식(포인터 사용)으로 바꾸기!

1
2
3
4
5
void swap(int *a, int *b) {
  int t = *a;
  *a = *b;
  *b = t;
}

함수 호출 방식 변경

1
swap(&a, &b);
  • &a, &b : 변수의 주소 전달
  • *a, *b : 해당 주소에 있는 실제 값 접근
  • 함수 내부에서 주소를 통해 main 함수의 변수 값을 직접 변경하게 된다.


switch문 실행

1
2
3
4
5
6
7
8
9
10
11
12
switch(조건값) {
  case 값1:
    실행문1;
    break;
  case 값2:
    실행문2;
    break;
  ...
  default:
    기본 실행문;
    break;
}
  • 조건값에 따라 어떤 case가 실행될지 결정된다.
  • break : 현재 case 블록을 빠져나간다.
  • break가 없으면, 일치한 case부터 아래 코드들이 계속 실행되는 fall-through가 발생한다.
  • default : 위의 case들 중 어떤 것도 일치하지 않을 경우 실행된다. (선택사항)

문제 6. 2차원 배열과 포인터 연산

1
2
3
4
5
6
7
8
9
#include <stdio.h>

int main() {
  int arr[3][3] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
  int* parr[2] = {arr[1], arr[2]};
  printf("%d", parr[1][1] + *(parr[1]+2) + **parr);

  return 0;
}
더보기

[정답]
21

[풀이]

1
int arr[3][3] = {1, 2, 3, 4, 5, 6, 7, 8, 9};

2차원 배열 arr행(row) 기준으로 순서대로 값이 채워진다.

1
2
3
4
5
6
7
arr[0] = {1, 2, 3}
arr[1] = {4, 5, 6}
arr[2] = {7, 8, 9}

arr[0][0] = 1, arr[0][1] = 2, arr[0][2] = 3
arr[1][0] = 4, arr[1][1] = 5, arr[1][2] = 6
arr[2][0] = 7, arr[2][1] = 8, arr[2][2] = 9


1
int* parr[2] = {arr[1], arr[2]};

parrint형 포인터 배열이다.
arr[1], arr[2]는 각각 배열이지만, 사용될 때는 첫 번째 요소의 주소로 변환된다.

인덱스의미가리키는 값
parr[0]arr[1] ➔ &arr[1][0]4부터 시작
parr[1]arr[2] ➔ &arr[2][0]7부터 시작

parr[0]은 {4, 5, 6}의 시작 주소를,
parr[1]은 {7, 8, 9}의 시작 주소를 가리킨다.


1
printf("%d", parr[1][1] + *(parr[1]+2) + **parr);
  • parr[1][1]
    • parr[1]arr[2]의 시작 주소(&arr[2][0])를 가리킨다.
    • parr[1][1]arr[2][1]
    • 값 : 8
  • *(parr[1]+2)
    • parr[1]arr[2][0]의 주소
    • (parr[1]+2) ➔ 2칸 이동 ➔ arr[2][2]
    • *(parr[1]+2) ➔ 해당 위치의 값
    • 값 : 9
  • **parr
    • parr은 포인터 배열이므로, 첫 번째 요소(parr[0])의 주소를 의미한다.
    • *parrparr[0]arr[1]&arr[1][0]
    • **parrarr[1][0]
    • 값 : 4

[최종 계산]
8 + 9 + 4 = 21
따라서 출력값은 21이다.

문제 7. 포인터를 이용한 문자열 복사와 널 문자 반복 구조

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stdio.h>
#include <string.h>

void sumFn(char* d, const char* s) {
  while (*s) {
    *d = *s;
    d++;
    s++;
  }
  *d = '\0';
}

int main() {
  const char* str1 = "first";
  char str2[50] = "teststring";
  int result = 0;
  sumFn(str2, str1);

  for (int i = 0; str2[i] != '\0'; i++) {
    result += i;
  }
  printf("%d", result);

  return 0;
}
더보기

[정답]
10

[풀이]
문자열 포인터와 문자 배열의 차이

1
const char* str1 = "first";
  • str1은 문자열 상수 “first”의 시작 주소를 가리키는 포인터이다.
  • “first”는 읽기 전용 메모리 영역(문자열 리터럴 영역)에 저장된다.
  • *str1 = 'A'와 같이 값을 변경하려고 하면 런타임 오류가 발생한다.
1
char str2[50] = "teststring";
  • str2문자 배열이다.
  • 배열 내부는 수정 가능한 메모리 공간이다.
  • 다른 문자열을 복사해 넣을 수 있다.


1
sumFn(str2, str1);
1
2
3
4
5
6
7
8
void sumFn(char* d, const char* s) {
  while (*s) {
    *d = *s;
    d++;
    s++;
  }
  *d = '\0';
}
s가 가리키는 값d에 복사d의 상태
‘f’‘f’“f”
‘i’‘i’“fi”
‘r’‘r’“fir”
’s’’s’“firs”
‘t’‘t’“first”
‘\0’종료“first\0”
1
2
3
4
// 널 문자를 만날 때까지 반복한다는 의미의 코드이다.
while (*s) {}

while (*s != '\0') {}

반복 조건이 while (*s)이기 때문에 널 문자(‘\0’)를 만나기 전까지만 복사한다.

sumFn() 함수 실행 후, str2 == "first"가 된다. (기존 "teststring"은 덮어씌워진다)


1
2
3
4
5
6
// 문자열 길이만큼 인덱스를 누적함
for (int i = 0; str2[i] != '\0'; i++) {
  result += i;
}

printf("%d", result);

이제 str2는 "first\0"이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
str2[0] = 'f'
result = 0 + 0 = 0

str2[1] = 'i'
result = 0 + 1 = 1

str2[2] = 'r'
result = 1 + 2 = 3

str2[3] = 's'
result = 3 + 3 = 6

str2[4] = 't'
result = 6 + 4 = 10

str2[5] = '\0'
반복문 종료

따라서 최종 result값은 10이다.

문제 8. -> 연산자를 이용한 구조체 포인터 멤버 접근

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>

struct node {
  int data;
  struct node *Next;
};

int main() {
  struct node *head = NULL;
  struct node a = {10, 0};
  struct node b = {20, 0};
  struct node c = {30, 0};

  head = &a;
  a.Next = &b;
  b.Next = &c;

  printf("%d", head->Next->data);

  return 0;
}
더보기

[정답]
20

[풀이]
연결 리스트 (Linked List)
각 노드가 데이터와 다음 노드를 가리키는 포인터로 구성된 자료 구조이다.

1
2
3
4
struct Node {
  int data; // 데이터를 저장하는 변수
  struct node *Next; // 다음 노드의 주소를 저장하는 포인터
};


1
2
3
4
struct node *head = NULL;
struct node a = {10, 0};
struct node b = {20, 0};
struct node c = {30, 0};

각 노드는 다음과 같이 초기화된다.

1
2
3
a: data=10, Next=NULL
b: data=20, Next=NULL
c: data=30, Next=NULL


연결 관계 설정

1
2
3
head = &a; // head가 a의 주소를 가리킴
a.Next = &b; // a가 가리키는 주소는 b의 주소를 가리킴
b.Next = &c; // b가 가리키는 주소는 c의 주소를 가리킴
1
head -> a -> b -> c -> NULL
  • heada의 주소를 저장한다.
  • aNext 포인터는 b의 주소를 가리킨다.
  • bNext 포인터는 c의 주소를 가리킨다.
1
printf("%d", head->Next->data);
  • heada를 가리킴
  • head->Next
    • a.Next = &b
  • head->Next->data
    • b.data이기에 20이 된다.

따라서 20이 출력된다.


2024년 3회 정보처리기사 실기

문제 9. 일반 지역변수와 static 변수의 차이

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>

int func() {
  static int x = 0;
  x += 2;
  return x;
}

int main() {
  int x = 0;
  int sum = 0;
  for(int i = 0; i < 4; i++) {
    x++;
    sum += func();
  }
  printf("%d", sum);

  return 0;
}
더보기

[정답]
20

[풀이]

1
2
3
4
5
int func() {
  static int x = 0;
  x += 2;
  return x;
}
  • xstatic 지역변수이다.
  • 함수가 호출될 때마다 새로 생성되는 것이 아닌, 프로그램 시작 시 메모리가 할당되고, 해당 함수가 처음 실행될 때 초기화된다.
  • 이후 함수가 호출될 때마다 이전 값이 그대로 유지된다.
  • 즉, func() 함수가 호출될 때마다 x는 누적 증가한다.
1
2
3
4
int main() {
  int x = 0; // 지역변수 x를 0으로 초기화
  int sum = 0; // 합계를 저장할 변수 sum을 0으로 초기화
}
  • mainx는 일반 지역변수이다.
  • 반복문에서 x++가 되지만, 이 값은 func()x와 전혀 다른 변수이다.
1
2
3
4
for (int i = 0; i < 4; i++) {
  x++; // main의 x
  sum += func(); // static x 사용
}
imain의 xfunc()의 static x반환값sum
0x = 1x = 22sum = 2
1x = 2x = 44sum = 6
2x = 3x = 66sum = 12
3x = 4x = 88sum = 20
1
printf("%d", sum);

따라서 sum20이 된다.


static 지역변수

  • 프로그램 시작 시 메모리가 할당되고, 프로그램 시작 전에 0으로 초기화되며, 명시한 초기값으로는 한 번만 초기화된다.
  • 함수가 종료되어도 소멸되지 않는다.
  • 이전 호출의 값을 그대로 유지한다.
  • 유효 범위(scope)는 함수 내부이지만, 수명(lifetime)은 프로그램 종료 시까지이다.
  • 메모리 영역 : 데이터 영역

일반 지역변수

  • 함수(또는 블록) 호출 시 생성된다.
  • 블록이 끝나면 소멸된다.
  • 유효 범위와 수명이 모두 블록 내부로 한정된다.
  • 메모리 영역 : 스택 영역

문제 10. 포인터 이동과 값 교환

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <stdio.h>

struct Node {
  int value;
  struct Node* next;
};

void func(struct Node* node) {
  while (node != NULL && node->next != NULL) {
    int t = node->value;
    node->value = node->next->value;
    node->next->value = t;
    node = node->next->next;
  }
}

int main() {
  struct Node n1 = {1, NULL};
  struct Node n2 = {2, NULL};
  struct Node n3 = {3, NULL};

  n1.next = &n3;
  n3.next = &n2;

  func(&n1);

  struct Node* current = &n1;

  while (current != NULL) {
    printf("%d", current->value);
    current = current->next;
  }

  return 0;
}
더보기

[정답]
312

[풀이]

1
2
n1.next = &n3;
n3.next = &n2;
  • n1next 포인터에 n3의 주소를 저장한다.
  • n3next 포인터에 n2의 주소를 저장한다.
1
2
3
4
5
(주소 흐름)
n1  →  n3  →  n2  →  NULL

(value)
[1]    [3]    [2]


1
func(&n1);
1
2
3
4
5
6
7
8
void func(struct Node* node) {
  while (node != NULL && node->next != NULL) {
    int t = node->value;
    node->value = node->next->value;
    node->next->value = t;
    node = node->next->next;
  }
}
1
2
3
int t = node->value; // t = 1
node->value = node->next->value; // n1.value = 3
node->next->value = t; // n3.value = 1
1
2
3
4
n1  →  n3  →  n2

(value 변화)
[3]    [1]    [2]
1
node = node->next->next; // node가 n2를 가리키게 된다
1
2
3
4
n1  →  n3  →  n2
[3]    [1]    [2]
               ↑
              node


1
while (node != NULL && node->next != NULL)
  • node != NULL : OK
  • node->next != NULL : NO (n2.next = NULL)

반복문이 종료된다.


1
2
3
4
5
6
struct Node* current = &n1;

while (current != NULL) {
  printf("%d", current->value);
  current = current->next;
}
1
2
3
4
n1  →  n3  →  n2
[3]    [1]    [2]
↑
current

포인터를 다음 노드로 이동시키며, current->value값을 하나씩 출력한다.
따라서 312가 출력된다.

문제 11. 이중 포인터의 역참조

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>

void func(int** arr, int size) {
  for (int i = 0; i < size; i++) {
    *(*arr + i) = (*(*arr + i) + i) % size;
  }
}

int main() {
  int arr[] = {3, 1, 4, 1, 5};
  int* p = arr;
  int** pp = &p;
  int num = 6;

  func(pp, 5);
  num = arr[2];
  printf("%d", num);

  return 0;
}
더보기

[정답]
1

[풀이]

1
2
  int* p = arr; // p → arr[0]
  int** pp = &p; // pp → p
  • p는 배열 arr의 시작 주소를 저장하는 포인터
  • pp는 포인터 p의 주소를 저장하는 이중 포인터
1
func(pp, 5);
1
2
3
4
5
void func(int** arr, int size) {
  for (int i = 0; i < size; i++) {
    *(*arr + i) = (*(*arr + i) + i) % size;
  }
}

*(*arr + i) 뜯어보기

  1. arr == pp
  2. *arr == p
  3. (*arr) + i = p + i
  4. *(p + i) == arr[i]

*(*arr + i)arr[i]와 같다.
arr[i] = (arr[i] + i) % size와 동일하다.

i결과값변경 후 arr 배열
0(arr[0] + 0) % 5 = 3 % 5 = 3{3, 1, 4, 1, 5}
1(arr[1] + 1) % 5 = 2 % 5 = 2{3, 2, 4, 1, 5}
2(arr[2] + 2) % 5 = 6 % 5 = 1{3, 2, 1, 1, 5}
3(arr[3] + 3) % 5 = 4 % 5 = 4{3, 2, 1, 4, 5}
4(arr[4] + 4) % 5 = 9 % 5 = 4{3, 2, 1, 4, 4}

최종 배열 상태 : {3, 2, 1, 4, 4}


1
2
num = arr[2]; // num = 1
printf("%d", num);

따라서 1이 출력된다.


이중 포인터의 역참조

1
*(*arr + i)

이중 포인터를 두 번 역참조하여 실제 배열 원소에 접근하는 코드이다.

  • arr → pp (포인터의 주소)
  • *arr → p (배열의 시작 주소)
  • *(*arr + i) → arr[i] (실제 배열 원소)

* 연산자를 사용할 때마다 포인터를 한 단계씩 따라 들어가 실제 값에 도달하게 된다.


정리

시프트 연산자

비트를 이동시키는 연산자이다.

종류의미형식설명
»오른쪽으로 이동5 » 25의 이진표현을 오른쪽으로 2칸 이동
«왼쪽으로 이동5 « 25의 이진표현을 왼쪽으로 2칸 이동


문자 판별 함수 (isupper, islower, isdigit)

각각 문자가 대문자, 소문자, 숫자인지 판별하며, 결과로 참이면 0이 아닌 정수, 거짓이면 0을 반환한다.

  • isupper(c) : c가 대문자 알파벳(A~Z)인지 확인
  • islower(c) : c가 소문자 알파벳(a~z)인지 확인
  • isdigit(c) : c가 숫자 문자(0~9)인지 확인
    • 숫자 자체가 아니라, 문자 형태의 숫자여야 하며, 작은 따옴표로 감싸야 한다.


static 변수와 지역변수 차이

static 지역변수

  • 프로그램 시작 시 메모리가 할당되고, 프로그램 시작 전에 0으로 초기화되며, 명시한 초기값으로는 한 번만 초기화된다.
  • 함수가 종료되어도 소멸되지 않는다.
  • 이전 호출의 값을 그대로 유지한다.
  • 유효 범위(scope)는 함수 내부이지만, 수명(lifetime)은 프로그램 종료 시까지이다.
  • 메모리 영역 : 데이터 영역

일반 지역변수

  • 함수(또는 블록) 호출 시 생성된다.
  • 블록이 끝나면 소멸된다.
  • 유효 범위와 수명이 모두 블록 내부로 한정된다.
  • 메모리 영역 : 스택 영역