Study/C Language

씹어먹는 C언어 혼자 실습 (3) switch문

imaginefuture-1 2024. 10. 17. 18:37

 

/* 업그레이드 버전 */
#include <stdio.h>
int main() {
    int input;

    printf("마이펫 업그레이드\n");
    printf("무엇을 하실 것인지 입력하세요 \n");
    printf("1. 밥주기 \n");
    printf("2. 씻기기 \n");
    printf("3. 재우기 \n");

    scanf_s("%d", &input);

    switch (input) {
    case 1:
        printf("아이 맛있어 \n");
        break;

    case 2:
        printf("아이 시원해 \n");
        break;

    case 3:
        printf("zzz \n");
        break;

    default:
        printf("무슨 명령인지 못 알아 듣겠어. 왈왈 \n");
        break;
    }

    return 0;
}

break를 안넣으면 

그냥 쥬루루 출력된다

 

break문으로 switch문을 빠져나가지못해서 아래 case들까지 줄줄이 실행된거다

/* 영어 말하기 */
#include <stdio.h>
int main() {
  char input;

  printf("(소문자) 알파벳 읽기\n");
  printf("알파벳 : ");

  scanf("%c", &input);

  switch (input) {
    case 'a':
      printf("에이 \n");
      break;

    case 'b':
      printf("비 \n");
      break;

    case 'c':
      printf("씨 \n");
      break;

    default:
      printf("죄송해요.. 머리가 나빠서 못 읽어요  \n");
      break;
  }

  return 0;
}

 

 

사실, 여기에 의문이 드는 사람들도 있습니다. 아까 위에서 switch 문은 정수 데이터만 처리한다고 했는데 왜 여기서는 문자 데이터도 처리가 되는 것인가?
. 왜냐하면 컴퓨터는 문자와 숫자를 구분 못합니다. 컴퓨터는 문자를 모두 숫자로 처리한 뒤, 우리에게 보여줄 때 에만 문자로 보여주는 것이지요. 따라서, 문자 = 정수 라고 생각해도 거의 무방합니다.

 


 

4단원 문자입력받기 문장에서

 

"수많은 문자들을 어떻게 처리?

문자들을 숫자에 대응시키는 것, 숫자에 대응시킨다면 컴퓨터가 이것이 숫자인지, 아니면 문자인지 어떻게 알까요?

물론 알 방법은 없습니다.

단지 이 숫자를 '문자' 형태로 사용하거나 '숫자'형태로 사용하는 것이지요."

 

 

엇 뭔가 머리 아파지면서 의문이 들었다.. gpt야 도움!!

 

 

 컴퓨터는 수많은 문자들을 어떻게 처리? 문자들을 숫자에 대응시키는 것,
숫자에 대응시킨다면 컴퓨터가 이것이 숫자인지, 아니면 문자인지 어떻게 알까요?
물론 알 방법은 없습니다.
단지 이 숫자를 '문자' 형태로 사용하거나 '숫자'형태로 사용하는 것이지요.라고 했는데
더 자세히 설명하면 형태로 사용한거에 주소값을 넣어줘서 size를 알고 그 사이즈가 몇 바이트로 구성된걸 보고 컴퓨터가 0,1로 처리한다는거지?
과정이 그럼 컴퓨터는
1. 숫자나 문자 형태를 봄
2. 주소값의 사이즈를 봄
3. 사이즈 별 바이트 값을 확인 4. 0,1로 표시 라는건가?


gpt:

컴퓨터가 문자를 처리하는 방법과 숫자와 문자를 구별하는 과정은 여러 단계로 이루어져 있습니다. 기본적으로 컴퓨터는 모든 데이터를 이진수(0과 1의 조합)로 처리합니다. 문자와 숫자도 결국 이진수로 표현되지만, 그 처리 과정에서 여러 가지 체계가 있습니다. 아래에서 그 과정을 자세히 설명하겠습니다.

### 1. 문자와 숫자의 인코딩

- **문자 인코딩**: 컴퓨터는 문자를 숫자에 매핑하기 위해 문자 인코딩 방식을 사용합니다. 가장 일반적인 인코딩 방식은 ASCII, UTF-8 등이 있습니다. 예를 들어, ASCII에서는 'A'는 65, 'B'는 66으로 표현됩니다.

- **숫자 표현**: 컴퓨터는 정수, 부동소수점 숫자 등을 이진수로 표현합니다. 예를 들어, 정수 5는 이진수로 `00000101`로 표현됩니다.

### 2. 메모리와 주소

- **메모리 주소**: 모든 데이터는 메모리에 저장되며, 각 데이터는 특정한 메모리 주소를 갖습니다. 이 주소는 데이터가 저장된 위치를 나타냅니다. 예를 들어, 'A'라는 문자가 메모리의 특정 주소(예: 0x01FF)에 저장되면, 그 주소를 통해 해당 데이터를 참조할 수 있습니다.

- **데이터 크기**: 데이터의 크기(즉, 바이트 수)는 해당 데이터의 타입에 따라 다릅니다. 일반적으로:
  - 문자(예: char)는 1바이트.
  - 정수(예: int)는 4바이트.
  - 부동소수점 숫자(float)는 4바이트 또는 8바이트.

### 3. 데이터 타입과 처리

- **타입 정보**: 프로그래밍 언어에서는 변수를 선언할 때 데이터 타입을 명시합니다. 이 타입 정보는 컴파일러나 인터프리터가 해당 변수가 어떤 종류의 데이터(문자, 숫자 등)를 나타내는지를 알 수 있게 해줍니다. 예를 들어, C에서는 `char` 타입으로 선언된 변수는 문자 데이터로 처리되고, `int`로 선언된 변수는 정수 데이터로 처리됩니다.

### 4. 이진수 처리 과정

1. **입력**: 사용자가 데이터를 입력하면, 프로그램은 이를 처리합니다. 입력된 데이터는 해당 타입에 따라 인코딩됩니다.

2. **주소와 사이즈**: 변수에 저장된 데이터는 메모리의 특정 주소에 위치하며, 이 주소는 변수의 타입에 따라 크기가 결정됩니다. 예를 들어, `char` 타입은 1바이트, `int` 타입은 4바이트를 차지합니다.

3. **타입 확인**: 프로그램은 각 변수의 타입 정보를 기반으로 어떤 데이터가 저장되었는지 확인합니다. 이 정보는 메타데이터의 형태로 변수와 함께 저장됩니다.

4. **이진수 변환**: 컴퓨터는 모든 데이터를 이진수로 변환하여 처리합니다. 문자는 각 문자 인코딩에 따라 이진수로 변환되고, 숫자도 해당 이진수 형태로 표현됩니다.

5. **저장과 처리**: 메모리에 저장된 데이터는 주소와 크기를 기반으로 읽혀지고, 필요한 연산이 수행됩니다.

### 결론

- **형태에 따른 처리**: 컴퓨터는 각 데이터가 어떤 타입인지(문자, 숫자) 알고 있으며, 이를 바탕으로 메모리에서의 저장과 처리를 수행합니다. 데이터의 타입 정보는 프로그래밍 언어의 변수를 정의할 때 명시되며, 이 정보를 통해 컴퓨터는 해당 데이터의 크기와 형식을 인식합니다.

- **이진수로의 변환**: 모든 데이터는 결국 이진수로 변환되어 처리되며, 문자는 인코딩 방식에 따라 숫자로 매핑됩니다. 이 과정에서 컴퓨터는 메모리 주소와 크기를 기반으로 해당 데이터를 올바르게 처리합니다.



나:

아. 1. 입력된 데이터의 타입을 먼저 보고
2. 그 데이터의 주소값를 보고 그 주소값은 타입에 따라 크기가 결정된다.
사이즈를 확인하는게 주소값에 의해 결정된 값을 본다는거지?
그리고 그걸로 이진수로 변환하는거고

gpt:

네, 당신의 이해가 정확합니다! 컴퓨터가 데이터를 처리하는 과정은 다음과 같은 단계로 이루어집니다:

1. 데이터 타입 확인
입력된 데이터의 타입을 확인: 프로그램은 변수 선언 시 명시된 데이터 타입을 기반으로 입력된 데이터를 처리합니다. 이 타입 정보는 변수가 어떤 종류의 데이터를 저장하고 있는지를 알려줍니다.예를 들어, int, float, char 등의 데이터 타입이 있을 수 있습니다.

2. 메모리 주소와 크기 확인
주소값: 각 변수는 메모리의 특정 주소에 저장됩니다. 이 주소는 데이터가 물리적으로 저장된 위치를 나타냅니다.타입에 따른 크기 결정: 각 데이터 타입은 메모리에서 차지하는 크기가 다릅니다. 예를 들어:char: 1바이트int: 4바이트float: 4바이트double: 8바이트변수의 데이터 타입에 따라 메모리에서 차지하는 크기가 결정되고, 이 크기는 메모리 주소에서의 오프셋(offset)을 계산하는 데 사용됩니다.

3. 이진수로 변환
데이터의 이진수 변환: 메모리에 저장된 데이터는 결국 이진수로 변환됩니다. 모든 데이터는 0과 1의 조합으로 표현되며, 이진수는 컴퓨터가 이해할 수 있는 형태입니다.저장 및 처리: 이진수로 변환된 데이터는 메모리 주소에 저장되고, 컴퓨터는 이를 기반으로 필요한 연산을 수행합니다.

과정 요약
입력 데이터의 타입을 확인: 프로그램이 변수의 타입을 통해
어떤 종류의 데이터를 저장하고 있는지 인식합니다.
주소값 확인 및 크기 결정: 변수의 주소값을 통해 데이터의 메모리 위치를 확인하고,
타입에 따라 크기를 결정합니다.
이진수 변환: 최종적으로 데이터를 이진수 형태로 변환하여 메모리에 저장하고 처리합니다.
이 과정 덕분에 컴퓨터는 입력된 데이터를 올바르게 인식하고,
이를 바탕으로 다양한 연산을 수행할 수 있습니다.

나:

총 정리하자면
1. c언어나, python같은 고급언어들
2. 컴파일러(데이터 및 주소값에 의해 결정된 값 확인)
3. 어셈블리어로 변환(메모리가 실질적으로 데이터 주소를 찾는 과정을 보여주는 언어)
4. 기계어(0,1)로 변환


gpt:

정리하신 내용이 매우 잘 되어 있습니다! 아래는 각 단계에 대한 간단한 설명을 추가하여
더 명확하게 정리하겠습니다.

1. 고급 언어
C, Python 등의 고급 언어: 사용자가 이해하기 쉬운 형태의 코드로 작성됩니다. 이러한 언어는 추상화가 높아 개발자가 복잡한 세부 사항을 신경 쓰지 않고도 프로그램을 작성할 수 있게 해줍니다.

2. 컴파일러 또는 인터프리터
컴파일러 (C의 경우): 고급 언어로 작성된 코드를 기계어로 변환하는 프로그램입니다. 컴파일러는 데이터의 타입, 크기, 주소값 등을 확인하고 최적화를 수행하여 중간 코드를 생성합니다.인터프리터 (Python의 경우): 코드를 한 줄씩 읽어 실행하는 방식으로, 코드가 실행될 때마다 변환 과정을 거칩니다. 인터프리터는 컴파일러와 비슷한 기능을 하지만, 실행 시점에서 변환을 수행합니다.

3. 어셈블리어
어셈블리어: 컴파일러가 생성한 중간 코드로, 기계어와 1:1 대응되는 명령어를 포함합니다. 어셈블리어는 인간이 이해하기 쉬운 형태의 기계어로, 메모리 주소와 데이터 처리의 세부 사항을 보여줍니다. 이 단계에서 코드의 하드웨어 동작을 명확히 이해할 수 있습니다.

4. 기계어
기계어: 컴퓨터가 직접 이해하고 실행할 수 있는 이진수(0과 1)로 구성된 언어입니다. 모든 명령과 데이터는 이 형태로 변환되어 메모리에 저장되고, CPU가 이 기계어를 실행하여 실제 작업을 수행합니다.

전체 과정 요약

고급 언어: 사용자가 이해하기 쉬운 코드 작성.
컴파일러/인터프리터: 코드 분석 및 타입, 크기, 주소값 확인 후 어셈블리어 또는 중간 코드로 변환.
어셈블리어: 기계어와 직접적으로 대응되는 명령어로 변환, 메모리 주소와 데이터 처리의 세부 사항을 보여줌.기계어: CPU가 직접 이해하고 실행할 수 있는 이진수로 변환되어 최종적으로 컴퓨터에서 실행.

이와 같은 프로세스를 통해 고급 언어로 작성된 프로그램이 실제로 컴퓨터에서 실행될 수 있는 형태로 변환됩니다. 각 단계는 컴퓨터가 데이터를 효율적으로 처리하고, 하드웨어와 상호 작용하는 데 필수적입니다.




아 혼공컴퓨터구조원리에서 본 내용이 단박에 정리되었다.
뒤섞인 공부 정보들이 아주 깔끔히 정리되었군..굳

아..이 글은 switch글인데 맞다 다시 switch로 돌아가보자면




"정말로
 switch문이 우리에게 필요한가?
 if - else로 다 해결되는데 왜 귀찮게
 switch문을 만들었을까? 차이는 단지 겉으로 얼마나 깔끔한지가 다를 뿐인데...
내부적으로 switch문과 if-else 와는 차이가 없나요?"

 

저자분은 이렇게 설명해주신다

 

일단 if 문의 경우 각 경우 마다 값들을 비교 합니다. 위 경우 값을 3 번 비교하겠네요. 왜냐하면 if 가 1 번, else if 가 2 번이고 else 의 경우 값의 비교 없이 자동으로 처리되는 것이므로 총 3 번 비교하게 됩니다. 즉, if 문을 이용하면 각 case 의 경우 비교하게 되므로 최악의 경우 모든 case 에 대해 값을 비교하는 연산 (어셈블리어에서는 CMP 연산을 합니다.)을 시행하게 됩니다.

 

그런데 switch 문은 사뭇 다릅니다. switch 의 경우 내부적으로 jump table 이라는 것을 생성합니다. 이 때, jump table 의 크기는 case 의 값들에 따라 달라지는데, 예를 들어서 어떤 switch 문의 경우 case 1: ~ case 10: 까지 있었다고 합시다. 그렇다면 jump table 에는 값들이 0 부터 9 까지 들어가게 됩니다. 여기서 우리는 왜 case 값: 할 때, '값' 부분에 변수가 위치하면 안되는지 알게 됩니다. jump table 은 프로그램 초기에 작성 되기 때문에 이미 switch 문이 실행되기 전에 jump table 이 작성되게 됩니다. 따라서, '값' 부분에 변수가 들어가게 되면 jump table 에 무엇이 올지 알 수 없으므로 변수를 사용하면 안되는 것입니다.
이 값들은 무엇을 의미하냐면 각 case 별로 명령들이 위치한 곳의 주소를 가리키는데 예를 들어서 1 인 지점으로 점프하게 되면 "아이 시원해" 가 나오고 0 인 지점으로 점프하게 되면 "아이 맛있어" 라고 출력하라는 내용의 명령문들이 나옵니다. 이제, 변수의 값에 따라 변수가 3 이라면 jump table 의 3 번째 원소를 찾아서 그 값에 해당하는 곳으로 점프하게 됩니다.

 

일단 이 답변을 읽고 두가지 의문이 들었다.

 

1. case 1~10인데 왜 jump table에는 0~9까지 값이 들어가는거지 (1~10으로 들어가야하는거아닌가?)

 

2. switch문이 실행되기전에 jump table이 작성되게되서 값 부분에 변수가 들어가게되면

jump table에 무엇이 올지 알수없으므로 변수를 사용하면 안된다는 말

 

->이미 정해놨으니까 변수가 들어가면 계속 다양한 상황에서 값이 바뀌니까

그런건가 대략적으로 이해는 되는데 뭔가 구체적으로 상황이 안그러졌다.

 

 

지피티야 지피티야!

 


 

gpt:

 

`switch` 문에서 `case` 라벨은 주어진 값을 기준으로 분기합니다. 각 `case`는 특정 값에 대해 실행할 코드를 지정하는 역할을 합니다. 예를 들어, `case 1:`과 `case 10:`이 있다고 할 때, 이는 각각 1과 10에 대한 분기를 의미합니다.

### 왜 0-9가 들어가는가?

C/C++ 및 여러 다른 프로그래밍 언어에서 `switch` 문은 주어진 값에 대한 정수 범위를 표현할 수 있습니다. 그러나 주의할 점은 `case` 라벨은 해당 정수 값에 대한 분기를 지정하기 때문에, `case 1:`부터 `case 10:`까지의 범위를 사용하는 경우, 실제로는 1~10이 아닌 0~9의 인덱스를 가지는 배열에서 0~9의 값을 사용하는 경우가 많습니다.

### 예시

```c
switch (value) {
    case 1:
        // do something
        break;
    case 2:
        // do something
        break;
    ...
    case 10:
        // do something
        break;
    default:
        // do something
}
```

이 경우, `value`가 1일 때 `case 1:`이 실행되고, `value`가 10일 때 `case 10:`이 실행됩니다. `jump`나 `table`에는 이와 같은 `case`에 대한 분기 정보가 저장됩니다.

### Jump Table의 구조

Jump Table은 각 `case`에 대한 포인터 또는 인덱스를 저장하는 배열로 구성됩니다. 이 배열의 인덱스는 주어진 `case` 값에서 가장 낮은 값을 빼서 사용합니다. 예를 들어:

- `case 1`의 인덱스는 `1 - 1 = 0`
- `case 2`의 인덱스는 `2 - 1 = 1`
- ...
- `case 10`의 인덱스는 `10 - 1 = 9`

따라서 `jump table`은 0부터 9까지의 값을 가지게 됩니다.

### 결론

- `switch` 문의 `case` 라벨은 주어진 값에 대한 분기를 정의합니다.
- Jump Table은 각 `case` 라벨의 인덱스에서 가장 낮은 값을 빼서 생성됩니다.
- 따라서 `case 1:`에서 `case 10:`까지의 경우, Jump Table의 값은 0~9가 됩니다.

 


나:

 

 
Jump Table은 각 case에 대한 포인터 또는 인덱스를 저장하는 배열로 구성됩니다.
이 배열의 인덱스는 주어진 case 값에서 가장 낮은 값을 빼서 사용합니다 부분을 더 자세히 설명해줄 수 있어? 가장 낮은 값을 빼서 사용하는 이유가 뭐야?

 

 

gpt:

 

Jump Table은 `switch` 문에서 효율적으로 분기 처리를 하기 위해 사용하는 자료구조입니다. 각 `case`에 대한 분기 주소를 배열 형태로 저장하여, 특정 값에 대해 직접적으로 점프할 수 있게 해줍니다. 아래에서 Jump Table의 작동 원리와 가장 낮은 값을 빼서 사용하는 이유를 자세히 설명하겠습니다.

 


### Jump Table의 구조

Jump Table은 일반적으로 다음과 같은 형태로 구성됩니다:

1. **배열**: 각 `case` 라벨의 주소나 인덱스를 저장하는 배열입니다.
2. **인덱스 계산**: 각 `case` 라벨의 값을 인덱스로 사용하기 위해, 가장 낮은 값을 뺀 결과를 인덱스로 사용합니다.

### 예시

가정해보겠습니다. 다음과 같은 `switch` 문이 있다고 합시다:

```c
switch (value) {
    case 1:
        // do something for case 1
        break;
    case 2:
        // do something for case 2
        break;
    case 3:
        // do something for case 3
        break;
    case 10:
        // do something for case 10
        break;
}
```

이 경우, `value`의 값이 1, 2, 3, 또는 10일 때 각각 다른 코드를 실행합니다. `case`가 1부터 10까지의 값을 가질 수 있지만, Jump Table을 만들 때는 이 값들을 인덱스로 변환할 필요가 있습니다.

### 인덱스 계산

Jump Table의 인덱스를 생성할 때, 가장 낮은 `case` 값을 빼는 이유는 배열의 크기를 줄이고 메모리를 효율적으로 사용하기 위함입니다. 이 예제에서 가장 낮은 `case` 값은 1입니다. 따라서:

- **인덱스 계산**:
  - `case 1`의 인덱스: `1 - 1 = 0`
  - `case 2`의 인덱스: `2 - 1 = 1`
  - `case 3`의 인덱스: `3 - 1 = 2`
  - `case 10`의 인덱스: `10 - 1 = 9`

위와 같이 계산하면 Jump Table의 인덱스는 0부터 시작하게 됩니다. 이 경우, 배열의 크기는 `10 - 1 + 1 = 10`이 됩니다.

### 배열 구성

이 배열에는 각 인덱스에 해당하는 `case`의 분기 주소를 저장합니다. 예를 들어:

```c
void* jump_table[10];  // 인덱스는 0~9

jump_table[0] = &case1_function;  // case 1의 주소
jump_table[1] = &case2_function;  // case 2의 주소
jump_table[2] = &case3_function;  // case 3의 주소
jump_table[9] = &case10_function;  // case 10의 주소
```

### Jump Table 사용 방법

1. `value`의 값을 가져와서 가장 낮은 값을 뺀 결과를 인덱스로 사용합니다.
2. 해당 인덱스를 Jump Table에서 찾아 점프합니다.

예를 들어 `value`가 10이라면:

```c
int index = value - 1;  // 10 - 1 = 9
void (*function_ptr)() = jump_table[index];  // jump_table[9]의 주소를 가져옵니다.
function_ptr();  // 해당 함수로 점프합니다.
```

### 결론

Jump Table에서 가장 낮은 값을 빼서 인덱스를 계산하는 이유는 배열의 크기를 최적화하고 메모리 사용을 효율적으로 하기 위함입니다. 이를 통해 `switch` 문에서 빠른 분기 처리가 가능해지며, 성능이 개선됩니다.

 

어 과정이 이해가 안됐다. 아마 포인터랑 배열을 자세히 공부해야 이해가 될 것 같다.

확실한건 결국 가장 낮은 값을 빼서 인덱스를 계산하는 이유는 메모리 사용을 효율적으로 하기위해서다.

 

이 복잡한 구조랑 명령어들은 전부 메모리랑 cpu를 효율적으로 사용하기 위해서니..

 

이제 두번째 질문에 답해줘 gpt야!

 

2. switch문이 실행되기전에 jump table이 작성되게되서 값 부분에 변수가 들어가게되면

jump table에 무엇이 올지 알수없으므로 변수를 사용하면 안된다는 말


 

gpt:

 

`switch` 문에서 Jump Table을 사용할 때, 기본적으로는 컴파일 타임에 `case` 라벨이 고정된 값들이 사용되어야 최적화된 Jump Table을 생성할 수 있습니다. 그러나 만약 `switch` 문의 `case` 값으로 변수를 사용하게 되면, 다음과 같은 이유로 Jump Table을 사용할 수 없게 됩니다.

### 1. 컴파일 타임 vs. 런타임

Jump Table은 **컴파일 타임**에 생성됩니다. 즉, `switch` 문이 컴파일될 때, 컴파일러는 모든 `case` 라벨의 값을 알고 있어야 하고, 이를 기반으로 Jump Table을 생성합니다. 만약 `case` 값이 변수가 된다면, 컴파일 타임에 그 값을 알 수 없으므로, Jump Table을 만들 수 없습니다. 

예를 들어:

```c
int value = 10;  // 이 값이 런타임에 결정된다고 가정
switch (value) {
    case 1:
        // do something
        break;
    case 2:
        // do something
        break;
}
```

이 경우, `value`가 10이 될 수도 있고, 다른 값이 될 수도 있습니다. 이 때문에 컴파일러는 `value`에 대한 Jump Table을 생성할 수 없습니다.

### 2. 동적 분기

`switch` 문에서 변수를 `case`에 사용하면, 해당 값에 대한 Jump Table 대신 일반적인 분기문(예: if-else 문)을 사용하게 됩니다. 이는 각 경우를 직접 비교하게 만드는 방식입니다.

예를 들어:

```c
int value = getValue();  // 런타임에 결정되는 값

switch (value) {
    case 1:
        // do something for case 1
        break;
    case 2:
        // do something for case 2
        break;
    // ...
}
```

여기서 `getValue()`는 런타임에 값을 반환하는 함수입니다. 이 경우, Jump Table을 생성할 수 없으므로, 컴파일러는 여러 비교를 수행하는 방식으로 코드를 생성합니다.

### 3. 성능 저하

Jump Table을 사용하면, 특정 `case`에 대해 빠르게 점프할 수 있지만, 변수를 사용하게 되면 성능이 저하됩니다. if-else 문에서는 각 경우를 순차적으로 비교해야 하므로, 최악의 경우 O(n)의 시간 복잡도를 가지게 됩니다. 반면, Jump Table은 O(1)의 시간 복잡도를 가집니다.

### 결론

- **정적 vs. 동적**: Jump Table은 컴파일 타임에 결정된 정적 값들을 기반으로 최적화되므로, `case`에 변수를 사용하면 동적으로 결정되는 값으로 인해 최적화된 Jump Table을 생성할 수 없습니다.
- **성능 차이**: 변수를 사용하게 되면 성능이 저하되고, 각 `case`에 대한 비교가 필요해집니다.
- **대안**: 만약 변수를 `case`로 사용해야 한다면, if-else 문과 같은 다른 조건문을 사용하는 것이 더 적절할 수 있습니다.

이러한 이유로 `switch` 문에서는 `case` 값으로 변수를 사용하는 것을 피하는 것이 좋습니다.

 

나:

 

value = 10이라는 값이 들어갔는데 이게 왜 10이될수도있고 다른 값도 될 수 있다는거야?


gpt:

`value = 10`이라는 코드는 단순히 `value`라는 변수가 10이라는 값을 갖도록 설정하는 것입니다. 그러나 이 변수의 값은 런타임에서 결정될 수 있으므로, 다양한 상황에서 다른 값을 가질 수 있다는 의미입니다. 여기서는 몇 가지 상황을 통해 이를 설명하겠습니다.

### 1. 런타임에 결정되는 값

변수는 프로그램이 실행되는 동안 여러 가지 값으로 변경될 수 있습니다. 예를 들어:

```c
int value;

// 어떤 조건에 따라 값이 달라질 수 있음
if (some_condition) {
    value = 10;
} else {
    value = 20; // 또는 다른 값
}
```

위 코드에서 `some_condition`이 참일 경우 `value`는 10이 되고, 거짓일 경우 다른 값이 될 수 있습니다. 이처럼 변수의 값은 프로그램의 흐름에 따라 변경될 수 있습니다.

### 2. 사용자 입력

사용자로부터 입력을 받을 때, 입력된 값이 변수에 저장될 수 있습니다. 예를 들어:

```c
int value;
printf("Enter a number: ");
scanf("%d", &value);  // 사용자가 입력한 값을 value에 저장
```

여기서 사용자가 10을 입력할 수도 있고, 5나 15와 같은 다른 값을 입력할 수도 있습니다. 이 경우 `value`는 런타임에서 사용자에 의해 결정되는 값이 됩니다.

### 3. 함수 호출

함수를 호출하여 반환값을 변수에 저장하는 경우에도 값이 달라질 수 있습니다.

```c
int getValue() {
    // 조건에 따라 다른 값을 반환
    if (condition1) return 10;
    else return 5; // 또는 다른 값
}

int value = getValue();  // getValue() 함수의 반환값에 따라 달라질 수 있음
```

이 예에서 `getValue()` 함수의 내부 로직에 따라 `value`의 값이 달라질 수 있습니다. 이는 프로그램의 흐름에 따라 동적으로 변화하는 값입니다.

### 결론

- **동적 결정**: 변수가 런타임에 여러 상황에 따라 값을 가질 수 있다는 것은, 프로그램의 흐름, 사용자 입력, 함수 호출 등에 따라 그 값이 동적으로 결정된다는 것을 의미합니다.
- **비결정성**: 특정 시점에 변수의 값을 알 수 없고, 그 값이 무엇인지 프로그램이 실행될 때까지는 확실히 알 수 없습니다. 
- **컴파일 타임 vs. 런타임**: 컴파일 타임에 결정된 값(예: `case 1`, `case 2`)과 달리, 런타임에 결정된 값(예: `value`에 사용자 입력 또는 함수 반환값)은 그 값이 무엇인지 알 수 없기 때문에 `switch` 문에서 Jump Table을 사용할 수 없습니다.

이러한 이유로, `switch` 문에서 `case` 값으로 변수를 사용하는 것은 일반적으로 바람직하지 않으며, 성능 및 최적화 측면에서도 좋지 않은 결과를 초래할 수 있습니다.

아 jump table 값은 정해놓았지만 (컴파일 타임에 결정)
변수는 런타임 중 값이 계속 다른 값을 가질 수 있다.


나:

런타임 결정은 여러 상황에 따라 바뀐다는의미인지는 알겠는데 컴파일 타임은 뭐야

gpt:

**컴파일 타임**과 **런타임**은 프로그램 실행의 두 가지 주요 단계로, 각각의 차이는 프로그램이 어떻게 처리되고 실행되는지를 설명합니다. 아래에서 각각의 개념을 자세히 설명하겠습니다.

### 컴파일 타임 (Compile Time)

- **정의**: 프로그램이 작성된 후, 소스 코드를 기계어 또는 중간 코드로 변환하는 과정을 컴파일 타임이라고 합니다. 이 단계에서는 코드의 구문 및 의미가 분석되고, 최적화가 이루어지며, 코드의 오류가 발견될 수 있습니다.
  
- **행동**:
  - **코드 분석**: 컴파일러는 소스 코드의 구문을 분석하여 문법 오류를 찾습니다.
  - **타입 체크**: 변수의 타입과 사용 방식을 확인하여 잘못된 타입 사용이 있는지를 검사합니다.
  - **최적화**: 성능을 개선하기 위해 코드 최적화가 이루어집니다.
  - **기계어 변환**: 최종적으로 코드가 기계어로 변환되어 실행 파일이 생성됩니다.

- **결과**: 컴파일 타임에 모든 코드 오류가 확인되며, 프로그램이 실행 가능한 형태로 변환됩니다. 이 과정에서 발생하는 오류는 소스 코드에서 수정해야 합니다.

### 런타임 (Run Time)

- **정의**: 프로그램이 실행되고 있는 동안의 시간을 런타임이라고 합니다. 이 단계에서는 프로그램이 실제로 사용자 입력을 처리하고, 메모리에 로드되어 명령을 수행하는 과정입니다.

- **행동**:
  - **메모리 할당**: 프로그램 실행 시 메모리에 변수를 할당하고, 필요한 리소스를 확보합니다.
  - **입력 처리**: 사용자 입력이나 파일 I/O 등의 작업을 수행합니다.
  - **동적 결정**: 조건에 따라 실행 경로가 결정되거나, 변수의 값이 런타임에 변경될 수 있습니다.
  - **오류 발생**: 런타임 중에 발생할 수 있는 오류(예: 배열 인덱스 초과, null 참조 등)는 실행 중에 발생합니다.

- **결과**: 프로그램이 실제로 동작하며, 이때 데이터 처리와 사용자 상호작용이 이루어집니다. 런타임 중에 발생하는 오류는 프로그램이 실행되는 동안에만 확인할 수 있습니다.

### 요약

- **컴파일 타임**: 프로그램 소스 코드가 기계어로 변환되는 단계로, 문법 및 타입 오류가 체크됩니다. 이 과정에서 오류가 발생하면 프로그램은 실행되지 않습니다.
- **런타임**: 프로그램이 실제로 실행되는 단계로, 사용자 입력 처리와 동적 결정 등이 이루어집니다. 런타임 중에는 실행 중 오류가 발생할 수 있습니다.

이와 같은 차이 덕분에 컴파일 타임에 오류를 사전에 파악할 수 있고, 런타임에 동적으로 변할 수 있는 요소들(예: 사용자 입력, 데이터 처리 등)을 관리할 수 있습니다.

 


생각해 보기

문제 1

switch 문의 '값' 부분에 왜 정수만 와야 되는지 아십니까?(난이도 : 中上)

 

->switch문의 값 부분에 왜 정수만 와야하는 질문에서 답변을 switch 는 현재 위치에서 index 만큼 jmp 하는 방식으로 처리되기 때문에 당연히 index 는 정수만 올 수 있습니다. 참고로 이와 같이 구현하는 이유는 여러번의 if-else 없이 그냥 jmp 한 번으로 원하는 코드 위치에 갈 수 있기 때문이지요.

 

? gpt야 바로 의문드는데

 

 index값은 1.1, 2.1 이렇게는 지정 못하는거야?

(복잡한걸 알지만 시도는 해볼 수 있지않냐는 질문, 아마 효율적인 면에서 떨어진다고 답변이 나올 것 같긴한데

어떤면에서 정확히 효율적이지 못한지 궁금했다)

 

gpt:

 

네, 맞습니다. switch 문의 case 값으로는 정수형 값만 올 수 있으며, 소수점 값(예: 1.1, 2.1)이나 다른 자료형(예: 문자열, 부울 등)은 사용할 수 없습니다. 이를 이해하기 위해 몇 가지 개념을 살펴보겠습니다.

1. Jump Table의 원리

switch 문은 효율적인 분기 처리를 위해 Jump Table을 사용하는 경우가 많습니다. 이 Jump Table은 각 case 값에 대한 주소나 인덱스를 미리 계산하여 배열 형태로 저장하고, 이를 통해 직접적으로 해당 코드 블록으로 점프(jump)하는 방식입니다.

  • 정수 인덱스: Jump Table은 정수 인덱스를 사용하여 요소에 접근합니다. 예를 들어, case 1:에 대한 인덱스는 0이고, case 2:에 대한 인덱스는 1입니다. 이렇게 배열의 인덱스가 정수로 되어 있어야만 컴파일러가 배열의 특정 요소를 효율적으로 참조할 수 있습니다.

2. 소수점 값 사용 불가

소수점 값(예: 1.1, 2.1)은 정수 인덱스와 다릅니다. 소수점 값을 사용하면, 컴파일러는 해당 값을 정수 인덱스로 변환할 수 없습니다. Jump Table의 인덱스는 항상 정수여야 하므로, 소수점 값은 사용할 수 없습니다.

  • 정수 vs. 실수:
    • 정수: 1, 2, 3 등과 같이 정해진 위치를 기준으로 Jump Table의 인덱스로 사용할 수 있는 값입니다.
    • 실수: 1.1, 2.1 등은 정수 인덱스로 변환할 수 없으므로, Jump Table에서 효율적으로 처리할 수 없습니다.

3. 분기 처리

switch 문의 장점은 여러 개의 if-else 문을 사용하는 대신, Jump Table을 통해 한 번의 점프로 원하는 코드 위치로 빠르게 이동할 수 있다는 점입니다. 소수점 값을 사용하면 Jump Table을 사용할 수 없기 때문에, 분기 처리의 효율성이 떨어지게 됩니다.

결론

  • 정수만 사용 가능: switch 문에서 case에 사용할 수 있는 값은 정수만 가능하며, 이는 Jump Table의 원리와 직접적으로 관련이 있습니다.
  • 소수점 값 불가: 소수점 값은 정수 인덱스가 아니기 때문에 Jump Table에서 사용될 수 없습니다.
  • 효율적인 분기: 정수 인덱스를 사용함으로써, 여러 번의 if-else 문을 피하고 효율적으로 코드를 실행할 수 있습니다.

이와 같은 이유로 switch 문에서는 정수만 사용 가능하며, 소수점 값을 지정할 수 없습니다.

 

나:

인덱스 값을 정수로 사용하는 이유도 결국 효율 때문인거지?
 
 
gpt:
 

네, 맞습니다! 인덱스 값을 정수로 사용하는 주된 이유는 효율성 때문입니다. 여기서 효율성이란 두 가지 측면에서 설명할 수 있습니다: 메모리 접근의 속도연산의 간결함입니다.

1. 메모리 접근의 속도

  • 배열 기반의 접근: 배열과 같은 데이터 구조에서 인덱스는 메모리의 특정 위치를 직접 참조하는 방법입니다. 정수 인덱스는 간단한 계산을 통해 메모리 주소를 빠르게 찾을 수 있게 해줍니다. 예를 들어, 배열의 첫 번째 요소가 메모리의 특정 주소에 위치할 때, 정수 인덱스를 통해 그 주소를 계산하는 방법은 다음과 같습니다:주소=기본 주소+(인덱스×데이터 크기)\text{주소} = \text{기본 주소} + (\text{인덱스} \times \text{데이터 크기})이 과정은 CPU가 빠르게 처리할 수 있는 단순한 산술 연산입니다.

2. 연산의 간결함

  • 명확한 표현: 정수는 명확하고 직관적인 방법으로 각 요소를 참조할 수 있게 해줍니다. 예를 들어, 배열의 첫 번째 요소는 array[0], 두 번째 요소는 array[1]와 같이 간단하게 표현할 수 있습니다.
  • 빠른 비교 및 계산: 정수 인덱스는 다른 자료형(예: 문자열, 실수 등)보다 비교 및 연산이 훨씬 빠릅니다. 정수 연산은 CPU에서 기본적으로 가장 빠른 연산 중 하나입니다.

3. 데이터 구조의 단순성

  • 불필요한 복잡성 제거: 만약 인덱스에 비정형 데이터(예: 문자열)를 사용한다면, 메모리 접근 방식이 복잡해지고, 각 요소를 참조하기 위한 추가적인 로직이 필요합니다. 이는 프로그램의 복잡성을 증가시키고 성능을 저하시킬 수 있습니다.

결론

정수 인덱스를 사용하는 것은 메모리 접근의 효율성을 극대화하고, 연산을 단순화하며, 프로그래밍 언어의 데이터 구조를 명확하고 간결하게 유지하는 데 도움을 줍니다. 이러한 이유로 대부분의 프로그래밍 언어에서 배열 및 리스트의 인덱스는 정수형으로 정의되어 있습니다.

 

컴퓨터의 이런 시스템, 구조 이유도 전부 효율때문인걸!

다만 정확히 어떤 이유로 효율적으로 되는지 궁금했다.

 

 

문제 2

앞서, switch 문이 내부적으로 처리 되는 부분에서 case 1: ~ case 10: 일 때 만 생각하였는데, 만약 case 1:, case 3:, case 4:, case 10: 과 같이 불규칙 적으로 switch 문이 적용된다면 컴퓨터는 jump table 를 어떻게 작성할까요 (난이도 : 最上)

 

gpt:

 

switch 문에서 case 라벨이 불규칙적으로 주어질 때(예: case 1:, case 3:, case 4:, case 10:), 컴파일러는 Jump Table을 작성하는 방식이 다소 복잡해질 수 있습니다. Jump Table을 사용하는 것은 일반적으로 연속적인 정수 값이 있을 때 효율적이지만, 불규칙한 경우에는 몇 가지 다른 접근 방식을 사용할 수 있습니다. 여기서는 Jump Table이 어떻게 구성될 수 있는지와 각 접근 방식에 대해 설명하겠습니다.

Jump Table의 구성

  1. 정수 범위 및 Gap 처리:
    • 만약 case 값들이 불규칙하다면, 컴파일러는 연속된 인덱스를 사용할 수 없으므로 Jump Table에 인덱스를 매핑할 때, 각 case 값의 위치를 수동으로 지정해야 합니다.
    • 예를 들어, case 1, case 3, case 4, case 10이 있을 때, 인덱스 배열을 사용하여 각 case 값을 지정할 수 있습니다.
  2. Sparse Jump Table:
    • 불규칙적인 case 값을 처리하기 위해, 컴파일러는 Sparse Jump Table를 사용할 수 있습니다. 이 구조는 주어진 case 값에 대해 인덱스를 매핑하고, 비어 있는 인덱스에 대해 기본적인 처리 또는 다른 방법을 사용합니다.
    • 예를 들어, 다음과 같은 Jump Table을 생성할 수 있습니다:
    case 1: // index 0
    case 3: // index 1
    case 4: // index 2
    case 10: // index 3
    
    // Jump Table
    void* jump_table[4]; // 인덱스는 0~3
    jump_table[0] = &case1_function; // case 1의 주소
    jump_table[1] = &case3_function; // case 3의 주소
    jump_table[2] = &case4_function; // case 4의 주소
    jump_table[3] = &case10_function; // case 10의 주소
    이 경우 Jump Table의 크기는 사용된 최대 case 수에 따라 다릅니다.
  3. 인덱스 매핑:
    • 각 case 값이 배열의 특정 인덱스에 매핑되어야 합니다. 예를 들어, 위의 Jump Table은 case 값의 위치에 따라 다음과 같이 매핑할 수 있습니다:
    switch (value) {
        case 1:
            // do something
            break;
        case 3:
            // do something
            break;
        case 4:
            // do something
            break;
        case 10:
            // do something
            break;
        default:
            // handle default case
    }
  4. Binary Search 또는 Hash Table:
    • 다른 방법으로는 Binary Search 또는 Hash Table을 사용할 수 있습니다. 이 방법들은 case 값이 불규칙적일 때 유용합니다.
    • Binary Search: case 값을 정렬한 다음 이진 탐색을 수행하여 원하는 case를 찾을 수 있습니다. 이 방법은 case 값의 수가 적고, 성능 저하를 최소화할 수 있는 경우에 적합합니다.
    • Hash Table: case 값을 키로 사용하고, 해당 값을 배열의 인덱스로 매핑하여 효율적으로 접근할 수 있습니다.

 

결론

  • Jump Table의 유연성: 불규칙적인 case 값을 가진 switch 문은 Jump Table을 작성할 때 특정한 전략을 사용해야 합니다. Sparse Jump Table 또는 다른 자료 구조를 사용하여 이를 처리할 수 있습니다.
  • 성능 고려: Jump Table은 효율적이지만, case 값이 불규칙할 경우 다른 구조(예: 이진 탐색 또는 해시 테이블)를 사용하는 것이 성능적으로 더 나을 수 있습니다.
  • 컴파일러의 최적화: 실제로, 이러한 최적화는 컴파일러의 구현에 따라 다르므로, 각 언어와 컴파일러가 Jump Table을 처리하는 방식은 다양할 수 있습니다.

이러한 방식으로, 컴퓨터는 불규칙한 case 값들에 대해 효과적으로 Jump Table을 작성할 수 있습니다.

 

 

아 그래서 각 case 위치를 수동으로 지정해줘야하고 그런 기법 중에
sparse jum table, binary search랑 hash table이 있구나..
그건 아직 내 지식 수준에서 이해하기엔...(먼산)
아무튼 신기했다. 또 방법은 있다는걸.

아니 이런 방법을 도대체 누가 계속 고안해내는걸까;;
진짜 컴퓨터 공부하는 사람은 천재가 아닐까.
폰노이만이 미래에서 온 사람이라고 표현하는 이유가 오늘도 절실히 와닿는다..


큰일났다..하루가 너무 짧다.. 뭐 하나 공부하면 그거 관련된거 계속 궁금증이 생겨서
파고 파다보면 시간이 너무 빨리지나간다..
진도 나가야 하는데 ㅜㅜ

오늘 네트워크랑 수학 공부도 해야하는데... 리눅스 명령어랑, 우분투 세팅은 언제 또..(눈물)

그래도 그 깊숙히 본질적인 이유에 대해 알면 컴퓨터의 모든 이 구조의 이유는
결국 자원을 효율적으로 사용하기 위해서다.
그게 신기하고 재밌다. 경이로움도 느껴진다..


'Study > C Language' 카테고리의 다른 글

씹어먹는 C언어 혼자 실습 (2)  (6) 2024.10.16
씹어먹는 C언어 혼자 실습 (1)  (1) 2024.10.16