Out Of Bounds

배열의 범위를 벗어난 메모리에 접근할 수 있는 취약점 입니다. 개발자가 인덱스에 대한 검사를 제대로 하지 않으면 발생하는 취약점으로 임의의 주소 읽기, 임의 주소 쓰기로 이어질 수 있습니다.

 

배열은 연속된 메모리 공간을 가지며 배열의 크기는 각 요소가 가지는 자료형 크기에 각 요소의 개수를 곱한 값입니다.

 

배열 등의 요소를 참조할 때 인덱스 값이 음수거나 배열의 길이를 벗어날 때 발생합니다.

개발자가 인덱스 범위에 대한 검사를 명시적으로 프로그래밍 하지 않으면, 프로세스는 앞서 배운 식을 따라 요소의 주소를 계산할 뿐 계산한 주소가 배열의 범 위 안에 있는지 검사하지 않습니다.

 

배열 참조에 사용되는 인덱스를 임의의 값으로 설정할 수 있다면 배열의 주소로부터 특정 오프셋에 있는 메모리의 값을 참조할 수 있습니다.

이를 배열의 범위를 벗어나는 참조라 하여 Out of Bounds라고 부릅니다.

실습 1

[그림 1] oob1.c(코드 참고: DreamHack)

 

[그림 1]과 같이 arr길이는 10입니다.  하지만 arr의 길이보다 작은 -1, 또는 큰 100을 인덱스로 지정하였을 때의 결과는 다음과 같습니다.

 

[그림 2] oob1 실행

출력1)

arr의 시작 주소는 0xbffff230입니다.

arr의 처음 주소인 arr[0]의 주소는 다음과 같습니다. 0xbffff230 + 4*0 = 0xbffff230

arr의 마지막 주소인 arr[9]의 주소는 다음과 같습니다. 0xbffff230 + 4*9 = 0xbffff254

 

출력2)

arr[-1]의 주소는 0xbffff230 + 4*-1 = 0xbffff22c 입니다.

arr[100]의 주소는 0xbffff230 + 4*100 = 0xbffff3c0 입니다.

 

출력2)의 예시로 arr배열의 인덱스를 통해 다른 주소에 접근할 수 있는 것을 확인할 수 있습니다.

 

실습 2

[그림 3]  cat secret.txt

 

[그림 4] oob2.c (코드 참고: DreamHack)

Out Of Bounds의 또 다른 예시로 docs 배열에 문자열 5개를 저장 후 입력 값을 인덱스로 하여 docs 배열의 요소를 출력하는 프로그램입니다.

read_secret함수를 통해 secret.txt의 문자열들을 받아와  secret 배열에 저장한 뒤 Out Of Bounds를 이용하여 출력해볼 예정입니다.

 

[그림 5] oob2 실행

입력값 -1을 인덱스로 하여 docs의 값을 출력합니다.

1-1 = 0, 5 - 1 = 4

0과 4 모두 docs 배열에서 사용할 수 있는 인덱스입니다.

[그림 6] secret.txt 출력

docs는 5개의 요소들을 가져 최대 인덱스가 4입니다. 하지만 6이 입력되어 docs에 인덱스 5의 값을 출력하게 되는데 이때 secret.txt를 출력하게 됩니다.

 

그 이유는 다음과 같습니다.

 

[그림 7] docs[idx-1] 출력 전 stack

arr[0] -> 0xbffff1dc + 4*0 = 0xbfff1dc -> 'one'가 존재하는 주소

arr[1] -> 0xbffff1dc + 4*1 = 0xbfff1e0 -> 'two'가 존재하는 주소

arr[2] -> 0xbffff1dc + 4*2 = 0xbfff1e4 -> 'three'가 존재하는 주소

arr[3] -> 0xbffff1dc + 4*3 = 0xbfff1e8 -> 'four'가 존재하는 주소

arr[4] -> 0xbffff1dc + 4*4 = 0xbfff1ec -> 'five'가 존재하는 주소

 

arr[5] -> 0xbffff1dc + 4*5 = 0xbfff1f0 -> 'secret.txt'의 문자열을 저장한 주소

 

위의 단계를 거쳐 secret.txt의 값을 출력할 수 있게 됩니다.

 

 

 

 

 

 

출처

https://dreamhack.io/lecture/courses/115

 

Memory Corruption: Out of Bounds

TBD이번 코스에서는 OOB 취약점이 발생하는 코드의 유형과 OOB를 공격해서 얻을 수 있는 효과에 대해 살펴보겠습니다.

dreamhack.io

 

RET Oerwrite

함수는 종료될 때 Return address(RET)를 pop하여 다음 명령어로 분기됩니다. 하지만 RET Overwrite를 이용해 되돌아갈 RET를 다른 주소로 변경한다면 해당 주소로 이동해 명령을 수행합니다. 

[그림 1] RET Overwrite

'BBBB'가 입력될 자리에 쉘코드의 주소가 들어간다면 함수가 리턴된 뒤 쉘코드로 인해 쉘이 실행될 수 있습니다.

 

BOF는 버퍼의 크기를 고려하지 않거나 입력 값의 길이를 검증하지 않는 경우에 발생할 수 있습니다.

 

BOF(Buffer OverFlow)

메모리를 다루는 데에 오류가 발생하여 잘못된 동작을 하는 프로그램 취약점입니다.(위키피디아)

 

(BOF를 이해하려면 함수가 호출되고 리턴될 때 발생하는 함수 프롤로그 및 에필로그에 대해 알아야합니다.)

 

입력된 데이터를 저장하는 버퍼에 지정된 버퍼크기 이상의 데이터를 입력할 경우 버퍼를 넘어선 다른 영역에 데이터를 덮어 씌우게 됩니다(RET Overwrite). 이때 다른 영역이라함은 함수가 호출될 때와 프롤로그를 수행할 때 저장된 Return address와 Base Pointer입니다.

 

실습

[그림 2] bugfile1.c

BOF를 실습할 수 있는 코드를 작성해줍니다. 해당 코드에서 strcpy는 argv[1]의 길이가 얼마인지 검사하지 않고 크기가 256인 buf에 복사합니다. 그리고 buf를 출력합니다.

[그림 3] bugfile1 실행-1

 

256이하의 길이만큼 입력하였을 때는 정상적으로 동작하는 것을 확인할 수 있습니다.

 

[그림 4] bugfile1 실행-2

 

그림 4와 같이 256이 넘는 264 크기만큼을 입력 후 출력했을 때 Segmentation fault가 발생합니다.

 

[그림 5] gdb -q bugfile1
[그림 6] strcpy 후 bp 설정

 

gdb 통해 bugfile을 디스어셈블한 것을 그림 5를 통해 확인할 수 있습니다. strcpy 후 buf에 어떤 값이 들어가는지 확인하기 위해 bp를 걸어줍니다(그림6).

 

[그림 7] buf의 주소

그림7을 통해 buf의 주소가 0xbffff0df인 것을 확인할 수 있습니다.

 

 

 

[그림 8] 스크립트 작성 - 1

그림 7을 구한 buf의 주소에 쉘 코드를 집어 넣으나 쉘이 실행되지 않습니다.

그 이유는 gdb를 통해 바이너리를 실행한 경우와 그냥 실행했을 때와의 차이 때문입니다.

1. 파일명 길이

2. $_ 환경변수 존재 여부

3. 절대경로/상대경로 여부

4. $OLDPWD 환경변수 존재 여부

이러한 차이로 인해 환경변수의 길이가 달라지게 되는데 이때 지역 변수의 주소도 변경되게 됩니다.

그 차이는 16(0x10)배수 만큼 차이나게 됩니다.

 

[그림 9] buf 주소 출력문 추가

실제 buf의 주소를 알기 위해 buf 주소 출력문을 추가해줍니다.

[그림 10] buf 주소 재출력

 

[그림 11] 스크립트 작성 - 2

buf의 주소를 0xbffff218로 바꾸어 주었음에도 쉘이 실행되지 않습니다.

스크립트 작성 후 buf의 주소가 0xbffff118로 변경되었습니다.

 

[그림 12] 쉘 동작

다시 주소를 buf의 주소를 0xbffff118로 변경하여 스크립트를 실행시키니 쉘이 동작하는 것을 확인할 수 있었습니다.

 

 

BOF에 취약한 함수

char *gets(char *buffer);

표준 입력(stdin)에서 문자열을 입력받아 사용자가 전달한 buffer에 저장하는 함수

 

int scanf(const char *format[,argument]...);

표준 입력 스트림에서 형식이 지정된 데이터를 읽습니다.

 

char *strcpy(char *strDestination, const char *strSource);

strSource의 문자열을 strDestination으로 복사합니다.

 

char *strcat(char *strDestination, const char *strSource);
strSource의 문자열을 strDestination의 뒤로 추가합니다. 

 

위 함수들의 문제점은 표준 입력을 통해 입력받는 문자열이나 문자열을 복사하고 이어붙일 때 길이는 제한이 없기 때문에 buffer의 크기보다 큰 입력값을 buffer에 전달할 수 있습니다.

 

그렇기에 입력값의 길이 검사 또는 지정한 길이만큼의 데이터를 이어붙이거나 복사하는 아래와 같은 함수를 사용합니다.

 

int scanf_s(const char *format [, argument]...);

 

char *gets_s(char *buffer, size_t sizeInCharacters);

 

char *strncpy(char *strDest, const char *strSource, size_t count);

 

char *strncat(char *strDest, const char *strSource, size_t count);

 
 

FSB(Format StringBug)

포맷스트링과 이것을 사용하는 printf() 함수의 취약점을 이용하여 RET의 위치에 쉘 코드의 주소를 읽어 쉘을 획득하는 해킹 공격입니다.

 

포맷 스트링이란?

일반적으로 사용자로부터 입력을 받아들이거나 결과를 출력하기 위해 사용하는 형식(%d, %f, %s etc...)

 

printf(variable) <- 이것이 문제가 되는 이유는 printf 함수에서 사용할 각종 형식 지시자(%d, %s, %c... 등)를 포함한 Format String으로 인식하게 됩니다.

 

 

%x: 스택에서 4바이트를 읽어와 16진수로 출력한다.
%x로 메모리 구조를 추측할 수도 있습니다.

%n: 앞에서 출력된 바이트수를 ESP 레지스터가 가리키는 주소에 덮어 씀

 

실습

[그림 13] easyfsb.c

그림 13에서 간단한 FSB를 알아보겠습니다.

 

1. i는 1로 초기화 되어 있고 i의 주소 및 값을 출력합니다.

2. 후에 printf를 이용하여 "test%n"를 출력하고 %n 자리에 i의 주소를 배치합니다.

 

 

[그림 14] i값 변경

그림 14를 통해 i값이 4로 변경된 것을 확인할 수 있습니다. 그 이유는 test를 출력하고 난 뒤 %n이 붙었기 때문에 test의 길이가 4이기 때문입니다.

 

위처럼 발생하는 문제 때문에 정확하고 안전하게 포맷스트링 함수를 사용해야 합니다.

 

 

 

 

 

 

 

 

 

 

참고

 

https://ko.wikipedia.org/wiki/%EB%B2%84%ED%8D%BC_%EC%98%A4%EB%B2%84%ED%94%8C%EB%A1%9C

 

버퍼 오버플로 - 위키백과, 우리 모두의 백과사전

다른 뜻에 대해서는 오버플로 문서를 참고하십시오. 버퍼 오버플로(영어: buffer overflow) 또는 버퍼 오버런(buffer overrun)은 메모리를 다루는 데에 오류가 발생하여 잘못된 동작을 하는 프로그램 취

ko.wikipedia.org

 

https://d4m0n.tistory.com/14?category=796362 

 

Buffer Overflow 기초

Buffer Overflow란? Buffer Overflow는 C 언어나 C++에서 버퍼에 데이터를 입력받을 때 입력 값의 크기를 검증하지 않아 버퍼가 흘러넘쳐 다른 변수나 메모리를 덮어 씌우게 되는 버그이다. 이 취약점을

d4m0n.tistory.com

https://eip343.tistory.com/25

 

(해커스쿨 LOB) gdb에서 확인한 쉘코드 시작 주소와 실제 바이너리에서의 주소가 다른 이유

해커스쿨에서 제공하는 Lord of BOF (redhat 6.2) 환경에서 테스트를 진행하였다. gremlin.c에서 환경변수를 출력하도록 변형한 gremlin2.c의 소스코드는 아래와 같다. gremlin2.c /* The Lord of the BOF : The..

eip343.tistory.com

 

 

https://kali-km.tistory.com/entry/BOF%EC%97%90-%EC%B7%A8%EC%95%BD%ED%95%9C-%ED%95%A8%EC%88%98

 

BOF에 취약한 함수

취약한 함수란 취약한 함수란 컴파일되기 이전에 프로그래머로부터 작성된 코드 중 버퍼 오버 플로우나 포맷 스트링 공격 등에 노출될 수 있는 함수를 뜻한다. 이러한 함수의 사용은 오류를

kali-km.tistory.com

 

http://wiki.hash.kr/index.php/%ED%8F%AC%EB%A7%B7%EC%8A%A4%ED%8A%B8%EB%A7%81_%EA%B3%B5%EA%B2%A9

 

포맷스트링 공격 - 해시넷

포맷스트링 공격(Format String Attack)이란 포맷스트링과 이것을 사용하는 printf() 함수의 취약점을 이용하여 RET의 위치에 셸 코드의 주소를 읽어 셸을 획득하는 해킹 공격이다. 기존에 널리 사용되던

wiki.hash.kr

 

https://velog.io/@2rlo/pwntools-%EC%82%AC%EC%9A%A9%EB%B2%95

 

pwntools 사용법

Pwntools 설치, 실행, 기능 정리 / 2020 / SISS / System / Tool

velog.io

1. Doubly LInked List(이중 연결 리스트, 이하 DLL)

 

이중 연결 리스트

 

DLL은 데이터를 저장하는 자료구조의 하나로 연결 리스트와 비슷합니다.

차이점이 있다면 다음 노드만이 아닌 이전 노드에 대해서도 연결되어 있습니다.

 

맨 첫 번째 노드는 Head가 되고 맨 마지막 노드는 Tail이 됩니다.

1
2
3
4
5
typedef struct linkedlist {
    struct Node* head;
    struct Node* tail;
    int count; //노드의 개수
}linkedlist;
cs

 

각 노드가 가지는 정보는 다음과 같습니다.

1
2
3
4
5
typedef struct Node {
    char data[10];
    struct Node* prev; // 이전 노드 포인터
    struct Node* next; // 다음 노드 포인터
}Node;
cs

 

 

사용된 함수는 다음과 같습니다.

1
2
3
4
5
6
7
void Create_Node(linkedlist* L);
void Read_Node(linkedlist* L);
void Update_Node(linkedlist* L);
void Delete_Node(linkedlist* L);
int Is_empty(linkedlist* L);
int Find_Node(linkedlist* L, char get_data[]);
void Clear_Node(linkedlist* L);
cs

 

 

실행 결과

초기 화면

 

1. 데이터 생성

 

2. 데이터 출력

 

3. 데이터 변환 후 출력

 

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
 
 
typedef struct linkedlist {
    struct Node* head;
    struct Node* tail;
    int count; //노드의 개수
}linkedlist;
 
typedef struct Node {
    char data[10];
    struct Node* prev; // 이전 노드 포인터
    struct Node* next; // 노드의 오른쪽 링크
}Node;
 
void Create_Node(linkedlist* L);
void Read_Node(linkedlist* L);
void Update_Node(linkedlist* L);
void Delete_Node(linkedlist* L);
int Is_empty(linkedlist* L);
int Find_Node(linkedlist* L, char get_data[]);
void Clear_Node(linkedlist* L);
 
 
int main() {
    int choice;
 
    linkedlist* L = (linkedlist*)malloc(sizeof(linkedlist));
    //Node* info;
    L->head = NULL;
    L->count = 0;
 
    printf("  Program start! \n\n");
 
    while (1) {
        printf("  1. Create a Data \n");
        printf("  2. Read all Data \n");
        printf("  3. Update a Data \n");
        printf("  4. Delete a Data \n");
        printf("  5. Exit \n");
        printf("  >> ");
        scanf_s("%d"&choice);
 
        if (choice == 1) {
            printf("  ========================================\n");
            Create_Node(L);
            printf("  ========================================\n");
        }
 
        if (choice == 2) {
            printf("  ========================================\n");
            Read_Node(L);
            printf("  ========================================\n");
        }
 
        if (choice == 3) {
            printf("  ========================================\n");
            Update_Node(L);
            printf("  ========================================\n");
        }
        if (choice == 4) {
            printf("  ========================================\n");
            Delete_Node(L);
            printf("  ========================================\n");
        }
        if (choice == 5) {
            printf("  ========================================\n");
            Clear_Node(L);
            printf("  ========================================\n");
            return 0;
        }
    }
}
 
void Create_Node(linkedlist* L) {
    Node* newNode = (Node*)malloc(sizeof(Node));
    printf("  Enter the Data: ");
    scanf_s("%s", newNode->data, sizeof(newNode->data));
 
    newNode->next = NULL;
 
    if (L->head == NULL) {
        L->head = newNode;
        L->tail = newNode;
        L->head->prev = L->head->next = NULL;
        L->tail->prev = L->tail->next = NULL;
        L->count++;
    }
    else {
        newNode->prev = L->tail;
        newNode->next = NULL;
        L->tail->next = newNode;
        L->tail = newNode;
        L->count++;
    }
 
    printf("  Create Node success!!\n");
    return ;
}
 
void Read_Node(linkedlist* L) {
    if (Is_empty(L))
        return 0;
 
    Node* info = L->head;
    int index = 1;
 
    while (info != NULL) {
        printf("  %d. Data : %s \n", index, info->data);
 
        if (info->next != NULL)
            printf("  ----------------------------------------\n");
 
        info = info->next;
        index++;
    }
    return ;
}
int Is_empty(linkedlist* L) {
    if (L->head == NULL) {
        printf("  There is no data.\n");
        return 1;
    }
    else
        return 0;
}
 
void Update_Node(linkedlist* L) {
    if (Is_empty(L))
        return 0;
 
    Node* node = L->head;
    char get_data[10];
 
    printf("  Enter the data want to update: ");
    scanf_s("%s", get_data, sizeof(get_data));
 
    if (Find_Node(L, get_data)) {
        printf("  There is no \"%s\".\n.", get_data);
        return 0;
    }
 
    while (strcmp(node->data, get_data))
        node = node->next;
    printf("  Enter the new data: ");
    scanf_s("%s", node->data, sizeof(node->data));
    printf("  Update Node success!!\n");
 
    return;
}
 
void Delete_Node(linkedlist* L) {
    Node* p = NULL;  // remove의 이전 노드
    Node* remove = L->head;
    char get_data[10];
 
    if (Is_empty(L))
        return 0;
 
    printf("  Enter the Data want to delete : ");
    scanf_s("%s", get_data, sizeof(get_data));
 
    if (Find_Node(L, get_data)) {
        printf("  There is no \"%s\". \n", get_data);
        return 0;
    }
 
    while (1) {
        if (!strcmp(get_data, remove->data)) {
            if (remove == L->head) {    //맨 처음 노드 삭제
                if (L->head->next == NULL) { //노드가 1개밖에 없는 경우에서의 삭제
                    L->head = NULL;
                    L->tail = NULL;
                    free(remove);
                    L->count--;
                    break;
                }
                L->head = remove->next;
                L->head->prev = NULL;
                L->head->next = remove->next->next;
                free(remove);
                L->count--;
                break;
            }
 
            else if (remove->next == NULL) {   //맨 마지막 노드 삭제
                L->tail = p;
                L->tail->prev = remove->prev->prev;
                L->tail->next = NULL;
                free(remove);
                L->count--;
                break;
            }
 
            else {  //중간 노드 삭제
                p->next = remove->next;
                remove->next->prev = p;
                free(remove);
                L->count--;
                break;
            }
        }
        p = remove;
        remove = remove->next;
    }
    printf("  Delete Node success!!\n");
    return;
}
 
int Find_Node(linkedlist* L, char get_data[]) {
    Node* node = L->head;
    int i = 0;
 
    for (i = 0; i < L->count; i++) {
        if (!strcmp(node->data, get_data))
            return 0;
        node = node->next;
    }
    return 1;
}
 
void Clear_Node(linkedlist* L) {
    Node* remove = L->head;
    Node* p = NULL;
 
    if (Is_empty(L)) {
        printf("  Program exit.\n");
        return 0;
    }
 
    while (remove != NULL) {
        free(remove);
        remove = p;
        if (p != NULL)
            p = p->next;
    }
    printf(" Program exit.\n");
    return ;
}
cs

 

 

참고

https://en.wikipedia.org/wiki/Doubly_linked_list

 

Doubly linked list - Wikipedia

From Wikipedia, the free encyclopedia Jump to navigation Jump to search Linked list data structure In computer science, a doubly linked list is a linked data structure that consists of a set of sequentially linked records called nodes. Each node contains t

en.wikipedia.org

 

 

 

2. 구구단 with 어셈블리어

.str1: 구구단 출력하기 위한 string -> "%d * %d = %d\n"

.str2: 단을 구분하기 위한 구분 선 string -> "===========\n"

 

.start: 메인 함수 시작을 위한 함수 프롤로그 및 스택 할당, -4(%ebp)에 2대 -> 2단부터 시작, Block2로 점프

 

.Block_2: -4(%ebp)의 값이 9와 같은지 확인

                같지 않다면 Block_5로 점프 -> 9단까지 반복해야하기 때문 

                같다면 메인 함수 에필로그 및 종료

            

.Block_5: -8(%ebp)에 1 할당 후 Block_3로 점프 -> -8(%ebp)에 곱할 수 할당

 

.Block_3: 9와 -8(%ebp)의 값이 같은지 확인

               같지 않다면 Block_4로 점프 -> 

               같다면 puts을 이용해 단을 구분하기 위한 구분선인 str2 출력 -> 하나의 단 출력 완료

               단수(-4(%ebp)) 증가

 

.Block_4:  연산을 위해 -4(%ebp) 값을 %eax에 저장 후 -8(%ebp)와 곱셈

                 printf 함수 호출을 위한 인자 배치

                 곱하는 값(-8(%ebp)) 1 증가

             

출력 결과

 

 

 

3. Stack 문서 요약

지역변수와 stack

C언어의 변수에는 지역 변수 전역 변수가 있습니다.

지역 변수는 함수 실행 시에만 할당되고, ‘전역 변수는 한 번 생성하면 어떤 함수에서든 사용할 수 있습니다. ‘지역 변수는 메모리의 stack에서 생성되고 사라지게 됩니다.

stack 선입후출(Last In First Out’의 구조를 가지고 있습니다. 쌓아올린 접시처럼 들어온 순서의 역순으로 데이터가 나가게 됩니다.

stack stack pointer(rsp)를 이용해 데이터를 넣고, 데이터를 반환합니다. 항상 맨 위에서부터 데이터를 넣고, 맨 위의 데이터부터 반환합니다.

 

함수가 호출될 때마다 함수 프롤로그, 함수가 반환될 때마다 함수 에필로그가 일어납니다.

 

함수 프롤로그

함수 프롤로그는 rbp를 스택에 넣고 rsp가 가지는 값을 rbp에 대입합니다. 그리고 사용할 만큼의 stack 공간을 할당합니다. 필요한 stack의 크기는 컴파일 될 때 계산됩니다.

 

함수 에필로그

함수 에필로그는 할당된 스택공간을 정리하기 위해 rbp가 가지는 값을 rsp에 대입합니다. 이전에 저장된 rbp값을 현재 rbp에 대입합니다. 그리고 프롤로그 때 저장한 이후 명령어를 실행합니다.

 

call ret

함수 프롤로그와 에필로그가 호출될 와 반환될 라면, 함수가 호출되기  call 명령어를 수행하고 반환된 ’ ret 명령어를 수행합니다.

‘call’ 명령어는 호출한 함수가 다시 제자리로 돌아온 뒤 실행해야할 코드의 주소 값을 스택에 push합니다. 그리고 해당 호출한 함수로 점프하여 함수 프롤로그를 진행합니다.

‘ret’ 명령어는 함수 에필로그를 진행한 뒤 call 명령어로 인해 저장된 다음 주소 값을 pop 하여 명령어 포인터 레지스터에 저장합니다.

‘ret’ 명령어는 ‘call’ 명령어와 단짝친구입니다. ‘call’ 명령어로 호출 된 코드면, ‘ret’ 명령어를 반드시 수행해야합니다.

 

스택에 저장된 변수를 참조할 경우엔 변수의 주소 값이 아닌 rbp에서 얼마나 떨어져 있는가를 이용합니다. 변수의 주소를 이용하는 것보다 base pointer rbp에서 오프셋을 계산하는 방식이 더 효율적이기 때문입니다.

 

 

 

 

 

 

 

 

 

 

 

HTTP

HTTP란 무엇인지 간단하게 살펴보겠습니다.

Hyptertext Trasnfer Protocol

여기서 Hypertext는 클라이언트의 선택에 따라서 이동이 가능한 조직화된 정보입니다.

 

어떠한 사이트에 접속 시 메인 페이지라 불리는 첫 번쨰 페이지 파일이 전송되어 브라웅저를 통해 출력합니다.

 

HTTP는 상태가 존재하지 않는 Stateless 프로토콜입니다.

서버는 클라이언트의 요청에 응답 후 연결을 끊습니다. 이러한 특징을 보완하고나 쿠키와 세션이라는 기술을 이용하여 상태 정보의 유지가 가능하게 됩니다.

 

요청 메시지(Request Message)

요청 메시지는 요청라인, 메시지 헤더, 메시지 몸체로 이루어져 있습니다.

요청 라인에는 요청방식에 대한 정보가 삽입되는데 대표적인 요청방식으로는 GET과 POST가 있습니다.

GET은 주로 데이터 요청, POST는 데이터 전송에 사용됩니다.

 

요청 라인의 GET /index.html HTTP/1.1은 다음과 같이 해석됩니다.

"index.html 파일을 요청(GET)하고 HTTP 프로토콜 버전 1.1 기준으로 통신하기를 원합니다."

 

요청 라인은 반드시 하나의 행으로 구성하여 전송하도록 약속이 되어 있으니, 전체 HTTP 요청 헤더 중 첫 번째 행을 추출해서 쉽게 요청 라인에 삽입된 정보를 확인할 수 있습니다.

 

메시지 헤더에는 요청에 사용된 브라우저 정보, 사용자인증정보 등 HTTP 메시지에 대한 부가적인 정보가 담깁니다.

 

메시지 몸체에는 클라이언트가 웹 서버에 전송할 데이터가 담기게 되는데, 이를 담기 위해서는 POST 방식으로 요청을 해야 합니다.

 

응답 메시지(Response Message)

응답 메시지는 상태 라인, 헤더 정보, 메시지 몸체로 이루어져 있습니다.

상태 라인에는 클라이언트의 요청에 대한 결과 정보가 담겨져 있습니다.

상태 라인의 HTTP1/1 200 OK는 다음과 같이 해석됩니다.

"HTTP 프로토콜 버전 1.1을 기준으로 응답하며, 당신의 요청은 성공적으로 처리되었습니다.(200 OK)"

 

메시지 헤더에는 전송되는 데이터의 타입 및 길이 정보 등이 담깁니다. 위의 예시에서는 다음과 같습니다.

"서버 이름: SimpleWebServer" 전송하는 데이터 타입은 text/html(html로 이루어진 텍스트 데이터)입니다. 그리고 데이터의 길이는 2048byte를 넘지 않습니다."

 

그리고 공백라인 이후에 메시지 몸체를 통해 클라이언트가 요청한 파일의 데이터가 전송됩니다.

참조 함수

sockaddr_in 구조체

bind 함수에 주소 정보를 전달하는 용도로 사용되는 구조체

 

int socket(int domain, int type, int protocol);

소켓 생성

 

int bind(SOCKET s, const struct sockaddr * name, int namelen); 구조체

소켓의 주소정보에 해당하는 것을 할당

IP주소와 PORT번호의 할당을 목적으로 호출되는 함수

 

int listen(int sockfd, int backlog);

연결 요청이 가능한 상태

 

int accept(int sockfd, struct sockaNddr *addr, socklen_t *addrlen);

연결요청에 대한 수락

 

ssize_t recv(int sockfd, void * buf, size_t nbytes, int flags);

성공 시 수신한 바이트 수(단 EOF 전송 시 0, 실패 시 -1 반환)

 

실습

listen 상태

 

접속 전

 

print Request and Response

 

접속 후

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
 
#define BUF_SIZE 1024
#define SMALL_BUF 100
 
char* request_handler(char* request);
char* load_file(FILE* fp);
void error_handling(char* message);
 
int main(int argc, char *argv[])
{
    int serv_sock;
    int clnt_sock;
 
    struct sockaddr_in serv_addr;
    struct sockaddr_in clnt_addr;
    socklen_t clnt_addr_size;
 
    char message[]="Hello World!";
    
    int recv_len;
    char request[BUF_SIZE];
    int opt=1;
    pthread_t t_id;
 
 
    if(argc!=2){
        printf("Usage : %s <port>\n", argv[0]);
        exit(1);
    }
    
    serv_sock=socket(PF_INET, SOCK_STREAM, 0);
    if(serv_sock == -1)
        error_handling("socket() error");
    else
        printf("socket() success\n");    
    setsockopt(serv_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    memset(&serv_addr, 0sizeof(serv_addr));
    serv_addr.sin_family=AF_INET;
    serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
    serv_addr.sin_port=htons(atoi(argv[1]));
    
    if(bind(serv_sock, (struct sockaddr*&serv_addr, sizeof(serv_addr))==-1 )
        error_handling("bind() error\n"); 
    else
        printf("bind() sucess\n");    
 
    if(listen(serv_sock, 5)==-1)
        error_handling("listen() error");
    else
        printf("listen() success\n");
 
    
    clnt_addr_size=sizeof(clnt_addr);  
    clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_addr,&clnt_addr_size);
    if(clnt_sock==-1)
        error_handling("accept() error");  
    else
        printf("accept() success\n");
    printf("Connection Request : %s: %d \n"
            inet_ntoa(clnt_addr.sin_addr), ntohs(clnt_addr.sin_port));
    printf("---------------------------------------------------------------\n");
    recv_len=recv(clnt_sock, request, sizeof(request),0);
    printf("<Request>\n%s\n", request);
    printf("---------------------------------------------------------------\n");
 
    char* response = request_handler(request);
    printf("<Response>\n%s\n", response);
    if(response != NULL)
        send(clnt_sock, response,(int)strlen(response),0);
 
    close(clnt_sock);    
    close(serv_sock);
    return 0;
}
 
char* request_handler(char *request)
{
    char *response = malloc(sizeof(char* BUF_SIZE);
    char *method, *path;
    method = strtok(request, " \t\r\n");
    path = strtok(NULL" \t");
    FILE * fp = fopen(path + 1"r");
    char* file = load_file(fp);
 
    FILE * cl = fopen(path + 1"r");    
    fseek(cl, 0, SEEK_END);
    int file_size = ftell(cl);
    char * content_l;
    sprintf(content_l,"%d",file_size);
 
    strcat(response, "HTTP/1.1 200 OK\r\n");
    strcat(response,"Location: ");
    strcat(response,path);
    strcat(response,"\r\n");
    strcat(response,"Contenti-Type: text/html; charset=utf-8\r\n");
    strcat(response,"Content-Length: ");
    strcat(response,content_l);
    strcat(response,"\r\n\n");
    strcat(response,file);
  
    free(file);
    return response;
 
}
 
char* load_file(FILE *fp){
    int i,j;
    
 
    char * buffer = malloc(sizeof(char* BUF_SIZE);
    memset(buffer,0,sizeof(buffer));
 
    j=fgetc(fp);
 
    for(i=0; j!=EOF; j=fgetc(fp), i++)
        buffer[i] = j;
 
    fclose(fp);
 
    return buffer;
 
}
 
void error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}
 
cs

 

많이 부족한 코드이지만 의도대로 Request를 받아오고 Response를 전달할 수 있었습니다.

좀 많이 애를 먹은 과제이지만 http의 원리를 통해 직접 만들어 볼 수 있는 과제였습니다.

 

 

 

 

 

 

 

※테스트를 진행하던 중 프로그램 실행이 되지 않고 bind error가 표시됩니다.

 

그 이유는 이미 쓰인 포트에 소켓이 다시 접속할 수 없기 때문입니다.(여기서 시간을 많이 잡아 먹었습니다...)

 

그래서 할당된 포트를 강제로 해제해야 합니다.

netstat -lntp 명령어를 통해 할당된 소켓 값, PID를 알아내어 해당 PID를 kill 합니다.sudo kill [PID]

저의 경우에는 netstat으로 PID를 확인한 경우 '-'(dash)가 나와 PID를 확인할 수 없었으나 관리자 권한으로 명령을 입력하니 확인할 수 있었습니다.

실행이 잘 되는 것을 확인할 수 있습니다.

 

참고

윤성우의 열혈 TCP/IP 소켓 프로그래밍

http://daplus.net/multithreading-%EC%BB%B4%ED%8C%8C%EC%9D%BC%ED%95%98%EB%8A%94-%EB%8F%99%EC%95%88-pthread%EC%99%80-lpthread%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90/

 

[multithreading] 컴파일하는 동안 -pthread와 -lpthread의 차이점 - 리뷰나라

의 차이 무엇입니까 gcc -pthread및 gcc -lpthread멀티 스레드 프로그램을 컴파일하는 동안 사용됩니까? 답변 -pthread 컴파일러에게 pthread 라이브러리에서 링크하고 스레드에 대한 컴파일을 구성하도록

daplus.net

https://kkalkkalparrot.tistory.com/51

 

[네트워크프로그래밍] 소켓 bind error 해결방법

이번 포스팅은 간단한 bind 에러 해결법이다. 네트워크프로그래밍을 하다보면 bind error가 나게 되는데, 그 이유는 이미 쓰인 포트에 소켓이 다시 접속할 수 없기 때문이다. 따라서 해결방법은 두

kkalkkalparrot.tistory.com

https://phoenixnap.com/kb/ubuntu-start-stop-restart-apache

 

Ubuntu: How To Start / Stop / Restart Apache Server

Updated Linux Tutorial on how to restart, start, & stop Apache web server on the Ubuntu operating system. Use the easy way now with a single shell command!

phoenixnap.com

https://reakwon.tistory.com/35

 

[C언어] 파일입출력 2 (fseek, ftell)

파일 입출력 2 간단하게 파일에서 읽고 쓰는 것은 이제 알겠습니다. 하지만 단순히 파일을 처음부터 차례대로 읽는 것이 아니라, 어떤 위치 이후에서 읽고 쓰는 것도 가능할까요? 할 수 있습니다

reakwon.tistory.com

 

1번 1073 : [기초-반복실행구조] 0 입력될 때까지 무한 출력하기2

입력

정수가 순서대로 입력된다.
-2147483648 ~ +2147483647, 단 개수는 알 수 없다.

 

입력 예시

7 4 2 3 0 1 5 6 9 10 8

 

출력

입력된 정수를 줄을 바꿔 하나씩 출력하는데, 0이 입력되면 종료한다.
(0은 출력하지 않는다.)

 

출력 예시

7

4

2

3

 

 

풀이

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
 
int main() {
    int num1;
    reload:
    scanf("%d"&num1);
    
    if (num1 != 0) {
        printf("%d\n", num1);
        goto reload;
    }
 
    return 0;
}
cs

 

위 처럼 goto문을 사용하여 reload라는 레이블로 이동하여 여러번 입력받을 수 있습니다.

변수는 하나이지만 입력버퍼에 데이터가 남아있기 때문에 해당 데이터가 공백을 기준으로 값이 변수에 들어갈 수 있었습니다.

 

goto문을 적절히 사용하지 않는 경우 "스파게티 코드"가 되어 코드가 복잡해질 수 있기 때문에 상황에 맞게 사용하는게 중요합니다.

goto문을 사용하지 않고 아래와 같이 문제를 해결할 수도 있습니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
 
int main() {
    int num1;
 
    scanf("%d"&num1);
    while (num1 != 0) {
        printf("%d\n", num1);
        scanf("%d"&num1);
    }
 
    return 0;
}
cs

 

 

 

2번 1090 : [기초-종합] 수 나열하기2

입력

시작 값(a), 등비의 값(r), 몇 번째 인지를 나타내는 정수(n)가
공백을 두고 입력된다.(모두 0 ~ 10)

 

입력 예시

2 3 7

 

 

출력

n번째 수를 출력한다.

 

출력 예시

1458

 

풀이

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
#include <math.h>
 
int main() {
    int a, r, n;
 
    scanf("%d %d %d"&a, &r, &n);
    printf("%d", a * pow(r, (n - 1)));
 
    return 0;
}
cs

 

pow 함수를 사용하여 등비 수열을 나타내려고 했었습니다.

하지만 위 그림과 같이 출력 값이 0으로 나와 이상하다고 생각하여 pow 함수에 대해 찾아보던 중 pow 함수의 반환 자료형이 double이라는 것을 알게 되었습니다.

 

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
#include <math.h>
 
int main() {
    int a, r, n;
 
    scanf("%d %d %d"&a, &r, &n);
    printf("%.f", a * pow(r, (n - 1)));
 
    return 0;
}
cs

 

pow의 반환 값에 맞게 double이라는 자료형을 출력하기 위해 %f를 사용하여 출력해주어 문제를 해결할 수 있었습니다.

 

지수 연산을 통해 나온 값이 정수 자료형이 아닌 실수 자료형으로 나오는게 신기하여 아래와 같이 테스트를 해봤었습니다.

 

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
#include <math.h?
 
int main() {
    float a = 2.5;
    
    printf("%f",pow(a, a));
 
    return 0;
}
cs

$2.5^{2.5}$의 결과로 9.882118이라는 값이 나왔습니다. 제곱하는 수가 실수(분수)인 경우도 있기 때문에 pow의 반환 자료형이 double이 된 것 같습니다.

 

3번 1091 : [기초-종합] 수 나열하기3

입력

시작 값(a), 곱할 값(m), 더할 값(d), 몇 번째 인지를 나타내는 정수(n)가
공백을 두고 입력된다.(a, m, d는 -50 ~ +50, n은 10이하의 자연수)

 

입력 예시

1- 2 1 8

 

출력

n번째 수를 출력한다.

 

출력 예시

-85

 

 

풀이

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
 
int main() {
    int a, m, d, n;
    scanf("%d %d %d %d"&a, &m, &d, &n);
 
    for (int i = 0; i < n-1; i++)
        a = a * m + d;
    printf("%d ", a);
    return 0;
}
cs

입력 예시와 출력 예시가 맞았기에 정답도 맞다고 생각하였는데 오답이 나왔었습니다.

계산되어 출력될 a 변수의 자료형이 잘못되었다고 판단되어 a의 자료형을 int에서 long long으로 바꾸어 주었습니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
 
int main() {
    long long a;
    int m, d, n;
    scanf("%lld %d %d %d"&a, &m, &d, &n);
 
    for (int i = 0; i < n - 1; i++)
        a = a * m + d;
    printf("%lld ", a);
    return 0;
}
cs

 

이와 같이 변수가 계산되고 다시 저장될 때의 크기까지 고려하여 자료형을 선택해야 한다는 것을 알 수 있었습니다.

 

 

 

4번 1097 : [기초-2차원배열] 바둑알 십자 뒤집기

입력

바둑알이 깔려 있는 상황이 19 * 19 크기의 정수값으로 입력된다.
십자 뒤집기 횟수(n)가 입력된다.
십자 뒤집기 좌표가 횟수(n) 만큼 입력된다. 단, n은 10이하의 자연수이다.

 

입력 예시

0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0

1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1

0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0

1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1

0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0

2

10 10

12 12

 

출력

십자 뒤집기 결과를 출력한다.

 

출력 예시

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

 

풀이

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
#include <stdio.h>
 
int main() {
    int badukpan[19][19], raw, col, num;
    for (raw = 0; raw < 19; raw++)
        for (col = 0; col < 19; col++)
            scanf("%d",&badukpan[raw][col]);
 
    scanf("%d"&num);
 
    for (int i = 0; i < num; i++){
        scanf("%d %d"&raw, &col);
        for (int j = 0; j < 19; j++)
            badukpan[raw - 1][j] = badukpan[raw - 1][j] == 0 ? 1 : 0;
        for (int j = 0; j < 19; j++)
            badukpan[j][col - 1= badukpan[j][col - 1== 0 ? 1 : 0;
    }
 
 
    for (raw = 0; raw < 19; raw++) {
        for (col = 0; col < 19; col++)
            printf("%d ", badukpan[raw][col]);
        printf("\n");
    }
    return 0;
}
cs

해당 좌표가 주어졌을 때 좌표 기준 행과 열을 모두 뒤집어야 하는 문제였습니다. 

3항 연산자를 이용하여 해당 값이 가지는 반대 값(0 또는 1)을 가지도록 만들어 줄 수 있었습니다.

 

 

 

5번 1099 : [기초-2차원배열] 성실한 개미

입력

10*10 크기의 미로 상자의 구조와 먹이의 위치가 입력된다.

 

입력 예시

1 1 1 1 1 1 1 1 1 1

1 0 0 1 0 0 0 0 0 1

1 0 0 1 1 1 0 0 0 1

1 0 0 0 0 0 0 1 0 1

1 0 0 0 0 0 0 1 0 1

1 0 0 0 0 1 0 1 0 1

1 0 0 0 0 1 2 1 0 1

1 0 0 0 0 1 0 0 0 1

1 0 0 0 0 0 0 0 0 1

1 1 1 1 1 1 1 1 1 1

 

출력

성실한 개미가 이동한 경로를 9로 표시해 출력한다.

 

출력 예시

1 1 1 1 1 1 1 1 1 1

1 9 9 1 0 0 0 0 0 1

1 0 9 1 1 1 0 0 0 1

1 0 9 9 9 9 9 1 0 1

1 0 0 0 0 0 9 1 0 1

1 0 0 0 0 1 9 1 0 1

1 0 0 0 0 1 9 1 0 1

1 0 0 0 0 1 0 0 0 1

1 0 0 0 0 0 0 0 0 1

1 1 1 1 1 1 1 1 1 1

 

풀이

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>
 
int main() {
    int ant_house[10][10], i ,j, x=2, y=2, ant;
 
    for (i = 0; i < 10; i++)
        for (j = 0; j < 10; j++)
            scanf("%d"&ant_house[i][j]);
    
    while (1) {
        if (ant_house[x - 1][y - 1!= 1) {
            ant = ant_house[x - 1][y - 1];
            ant_house[x - 1][y - 1= 9;
            if (ant == 2)
                break;
        }
 
        if (ant_house[x - 1][y] == 1)    
            x++;
        else
            y++;
    }
    for (i = 0; i < 10; i++) {
        for (j = 0; j < 10; j++)
            printf("%d ", ant_house[i][j]);
        printf("\n");
    }
    return 0;
}
cs

처음 정답을 제출하였을 때 Runtime Error:Segmentation fault 오류가 발생했었습니다.

먹이를 먹었을 때만 while문을 빠져 나오고, 먹이를 먹지 못한 경우에 대해 처리를 해주지 않아 while문이 끝나지 않았기 때문입니다.

 

그렇기에 개미가 움직이지 못하는 상황은 현재 자리에서 오른쪽과 아래쪽에 벽이 있을 경우이기 때문에 해당 조건을 추가해주어 문제를 해결할 수 있었습니다.

 

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
#include <stdio.h>
 
int main() {
    int ant_house[10][10], i ,j, x=2, y=2, ant;
 
    for (i = 0; i < 10; i++)
        for (j = 0; j < 10; j++)
            scanf("%d"&ant_house[i][j]);
    
    while (1) {
        if (ant_house[x - 1][y - 1!= 1) {
            ant = ant_house[x - 1][y - 1];
            ant_house[x - 1][y - 1= 9;
            if (ant == 2)
                break;
        }
 
        if (ant_house[x - 1][y] == 1) {
            if (ant_house[x][y - 1== 1)
                break;
            x++;
        }
        
        else
            y++;
    }
    for (i = 0; i < 10; i++) {
        for (j = 0; j < 10; j++)
            printf("%d ", ant_house[i][j]);
        printf("\n");
    }
    return 0;
}
cs

 

 

코드업 기초 100제를 마치며

기초 100제를 풀면서 C언어를 다시 공부할 수 있었습니다. (1009, 1016, 1100 미존재)간단히 풀릴 것이라 생각했던 문제들에 예상치 못한 오류와 실수로 오답이 만들어지기도 했지만 그럴 때마다 다르게 접근하여 재미있게 문제들을 해결할 수 있었습니다. 

1번 1035: [기초-출력변환] 16진 정수 1개 입력받아 8진수로 출력하기

입력

16진 정수 1개가 입력된다.
(단, 16진수는 영문 소문자로 입력된다.)

 

입력 예시

f

 

출력

8진수로 바꾸어 출력한다.

 

출력 예시

17

 

 

풀이

1
2
3
4
5
6
7
8
9
#include <stdio.h>
 
int main() {
    int num;
    scanf("%x"&num);
    printf("%o\n", num);
 
    return 0;
}
cs

위와 같이 문제를 해결할 수 있었습니다.

그러다 scanf가 궁금해져 scanf에 대해 찾아보다 다음과 같은 내용을 찾을 수 있었습니다.

 

"scanf는 입력된 값들을 왼쪽부터 읽는다. 이때 서식문자에 알맞은 형식의 데이터가 입력되었는지 확인한다.

알맞지 않은 데이터일 경우 다음 서식문자를 이용하여 입력 값을 읽습니다. 또한 빈 칸은 필요에 따라 무시되기도 한다."

 

 
scanf("%d%d%f%f", &i, &j, &x, &y);
ㅇcs

 

위와 같은 코드를 통해 "1-20.3-4.0e3"을 입력하면 다음과 같이 동작합니다.

1. 첫 번째 %d를 통해 정수 1을 입력받고 뒤에 오는 "-" 정수형에서 숫자 뒤에 올 수 없으므로 그 다음 서식문자를 통해 입력된다. (i = 1)

2. 두 번째 %d를 통해 첫 번째 %d에서 받지 못한 "-"를 입력 받는다. 그리고 정수형에서는 "."을 받을 수 없으므로 "-20"까지 입력받는다. (j = -20)

3. 첫 번째 %f를 통해 "."부터 입력을 받는데 실수에서 "-"는 숫자 뒤에 올 수 없으므로 ".3"까지 입력된다. (x = .3)

4. 두 번째 %f를 통해 나머지 값들인 "-4.0e3"을 입력받는다.(y = -4.0e3)

 

따라서 위 예시를 통해 scanf는 서식문자열에 따라 입력 값을 검사하여 변수에 저장하는 것을 알 수 있었습니다.

 

 

2번 1041 : [기초-산술연산] 문자 1개 입력받아 다음 문자 출력하기

 

입력

영문자 1개가 입력된다.

 

입력 예시

a

 

출력

다음 문자를 출력한다.

 

출력 예시

b

 

 

풀이

1
2
3
4
5
6
7
8
9
#include <stdio.h>
 
int main() {
    char c1;
    scanf("%c"&c1);
    printf("%c", c1+1);
    
    return 0;
}
cs

 

입력을 캐릭터로 받아 해당 값에 상수를 더하여 입력 문자 다음의 문자를 얻을 수 있습니다.

위 내용이 가능한 이유는 int이하의 변수 타입과 int와 연산을 할 경우 int 이하의 변수 타입이 int로 형변환 되기 때문입니다. float과 int의 연산에서 결과가 float으로 나오는 것과 같다고 볼 수 있을 것 같습니다.

 

 

 

 

3번 1043 : [기초-산술연산] 정수 2개 입력받아 나눈 나머지 출력하기

 

입력

정수 2개(a, b)가 공백을 두고 입력된다.
단, 0 <= a, b <= +2,147,483,647, b는 0이 아니다.

 

입력 예시

10 3

 

출력

a 를 b로 나눈 나머지를 출력한다.

 

출력 예시

1

 

 

풀이

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
 
int main() {
    unsigned int a;
    int b;
 
    scanf("%u %d"&a, &b);
 
    printf("%d", a % b);
    return 0;
}
cs

a의 범위가 0이상이므로 unsigned int로 지정해주었고, b는 int로 지정해주어 문제를 해결하였습니다.

 

그러다 unsigned 자료형에 음수값을 넣었을 때 어떻게 되는지 궁금하여 테스트를 해보았었는데 음수 값이 나왔었습니다.

1
2
3
4
5
6
7
#include <stdio.h>
 
int main() {
    unsigned int a = -1;
    printf("%d", a);
    return 0;
}
cs

 

 

1. printf 에서 맨 앞의 format string 제외하면 나머지는 va_arg/va_list 로 넘어가기때문에 format string 뒤쪽의 argument 에 대해서는 타입 정보를 못 받습니다.

2. 즉, 순수하게 1111...1111 같은 비트 덩어리만 넘어가는거고, 저 bit 를 해석하는건 %d 에 달려있습니다.

3. %d 는 int 에 대응되는거니 저 bit 덩어리를 int 로 해석해서 -1 이 되는 것이다.

4. 같은 이유로, printf 에서는 float 이나 double 이나 둘 다 %f 를 써도 되지만, scanf 에서는 %f, %lf 구분을 해줘야 합니다.

 

1
2
3
4
5
6
7
#include <stdio.h>
 
int main() {
    unsigned int a = -1;
    printf("%u", a);
    return 0;
}
cs

 

unsigned int가 표현할 수 있는 가장 작은 값인 0을 지나 언더플로우가 일어났기 때문에 4294967295와 같은 값이 나오게 된 것을 알 수 있었습니다.

 

 

 

 

4번 1057 : [기초-논리연산] 참/거짓이 서로 같을 때에만 참 출력하기

 

입력

1 또는 0의 값만 가지는 2개의 정수가 공백을 두고 입력된다.

 

입력 예시

0 0

 

출력

참/거짓이 서로 같을 때에만 1을 출력하고, 그 외의 경우에는 0을 출력한다.

 

출력 예시

1

 

 

풀이

같은 참/거짓일 때 1값을 출력하므로 XNOR 연산을 이용합니다.

 

1
2
3
4
5
6
7
8
#include <stdio.h>
 
int main() {
    int num1, num2;
    scanf("%d %d"&num1, &num2);
    printf("%d"!!num1 == !!num2);
    return 0;
}
cs

 

!를 한 번 해주어서 불리언으로 만들고 다시 !를 해주어 원래 값이 가지는 참/거짓 값으로 변환해주었습니다.

하지만 위는 "=="이라는 비교연산자가 사용되었기 때문에 다시 논리연산자만을 이용하여 다시 풀었습니다.

 

1
2
3
4
5
6
7
8
#include <stdio.h>
 
int main() {
    int num1, num2;
    scanf("%d %d"&num1, &num2);
    printf("%d", (!num1 && !num2) || (num1 && num2));
    return 0;
}
cs

 

 

 

5번 1064 : [기초-삼항연산] 정수 3개 입력받아 가장 작은 수 출력하기

문제 설명

 

입력

3개의 정수가 공백으로 구분되어 입력된다.
-2147483648 ~ +2147483648

 

입력 예시

3 -1 5

 

출력

가장 작은 값을 출력한다.

 

출력 예시

-1

 

 

풀이

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
 
int main() {
    int num1, num2, num3;
    scanf("%d %d %d"&num1, &num2, &num3);
 
    printf("%d",  num3 < (num1 < num2 ? num1 : num2) ? num3: (num1 < num2 ? num1 : num2));
    return 0;
 
}
cs

삼항연산자에 대해 알고 있었지만 활용해 본 기억이 많이 없었습니다.

이번 문제를 통해 삼항 연산자의 참/거짓 부분을 깊게 만들어 세 수를 비교할 수 있었습니다.

 

 

 

 

 

1. 입력 받는 값 범위와 해당 값의 연산으로 인해 바뀌는 값의 범위를 생각하여 이에 알맞은 자료형을 선택해야 한다.

2. 캐릭터형과 상수의 덧셈이 가능하다.

3. 자료형이 다른 변수의 연산에서 크기가 작은 데이터 타입이 큰 데이터 타입으로 형변환 된다.

등과 같은 내용을 리마인드 할 수 있는 문제들이었습니다.

 

 

 

 

 

 

 

 

 

참고

https://buyandpray.tistory.com/70

 

[C/C++] scanf의 작동원리

scanf()는 사용자의 입력을 특정 형식(format string)에 맞춰 읽은 뒤 변수에 값을 저장한다. 이 형식(format string)에는 일반적인 문자(ex. a, b, c, 공백 등)나 변환 문자(conversion specification)가 들어갈..

buyandpray.tistory.com

https://cheerant.tistory.com/43

 

char형 연산

백준 1194번 문제에서 char형을 다루면서 생긴 의문이다. System.out.println('a'-'f');  --> -5 System.out.println('b'-'b'); --> 0 아스키코드 값이 연산 되는 것이라고 짐작은 했지만 정확하게 알고 넘어가기..

cheerant.tistory.com

 

https://www.clien.net/service/board/kin/6528386

 

[C++] unsigned int 변수에 음수가 저장되나요? : 클리앙

unsigned 는, 양수만 다루도록 한다고 알고있는데.. VS2008 환경에서 실제로 다음과 같이 실행해보면, unsigned int x = -1; printf("%d", x); 출력이 -1 로 나옵니다. 음.. 그럼 음수도 저장 된다고 봐야되는 거

www.clien.net

https://dojang.io/mod/page/view.php?id=32 

 

C 언어 코딩 도장: 7.2 오버플로우와 언더플로우 알아보기

만약 정수 자료형에서 저장할 수 있는 범위를 넘어서면 어떻게 될까요? 다음 내용을 소스 코드 편집 창에 입력한 뒤 실행해보세요. integer_overflow.c #include int main() { char num1 = 128; // char에 저장할 수

dojang.io

 

1번 1023 : [기초-입출력] 실수 1개 입력받아 부분별로 출력하기

입력

실수 1개가 입력된다.
(단, 입력값은 절댓값이 10000을 넘지 않으며, 소수점 이하 자릿수는 최대 6자리까지이고
0이 아닌 숫자로 시작한다.)

 

입력 예시

1.414213

 

출력

첫 번째 줄에 정수 부분을, 두 번째 줄에 실수 부분을 출력한다.

 

출력 예시

1

414213

 

풀이

1차 풀이

1
2
3
4
5
6
7
8
9
10
11
12
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
 
int main() {
    int a;
    int b;
    
    scanf("%d.%d"&a,&b);
    printf("%d\n%d",a,b);
    
    return 0;
}
cs

입력시 "."으로 구분하여 정수부와 소수부를 구분합니다.

 

2차 풀이

실수 하나만 바당 정수부와 소수부로 나눌 수 있을지 않을까란 생각이 들었습니다.

strtok 함수로 "."을 구분자로 하여 문자열을 자를 수 있었습니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
 
int main() {
    char *= malloc(sizeof(char)*12);
    scanf("%s", a);
 
    char* split_a = strtok(a, ".");
  
    while (split_a != NULL) {
        printf("%s\n", split_a);
        split_a = strtok(NULL".");
    }
    free(a);
    return 0;
}
cs

 

char * strtok(char * _String, char const *_Delimiter);

C언어서 문자열을 분리해주는 함수.(strtok = string tokenize)

두 번째 인자인 구분자를 싱글쿼터('')가 아닌 더블쿼터("")로 묶어주어야 합니다.

파이썬의 split과 달리 한 번에 구분자로 자를 수 없기 때문에 while문을 사용하여 문자열을 자릅니다.

 

NULL을 넣었을 때는 직전 strtok 함수에서 처리했던 문자열에서 잘린 문자열만큼 다음 문자로 이동한 뒤 다음 문자열을 자릅니다.

잘린 부분을 NULL로 바꾼 뒤 잘린 문자열의 포인터를 반환합니다.

 

즉, strtok 함수를 사용할 때는 처음에만 자를 문자열을 넣어주고, 그 다음부터는 NULL을 넣어줍니다.

이때 strtok 함수가 어떻게 구분자 이후의 주소를 알고 있는지 궁금하였는데 해당 값이 지역변수가 아니라 정적변수라면 가능하게 된다고 합니다.

 

 

 

 

 

2번 1025 : [기초-입출력] 정수 1개 입력받아 나누어 출력하기

 

입력

다섯 자리로 이루어진 1개의 정수를 입력받는다.

(단, 10,000 <= 입력받는 수<=99,999)

 

입력 예시

75254

 

출력

각 자리의 숫자를 분리해 한 줄에 하나씩 []속에 넣어 출력한다.

 

출력 예시

[70000]

[5000]

[200]

[50]

[4]

 

풀이

1차 풀이

아래와 같이 각 자리수 별로 변수를 만들어 저장하여 10,000부터 1까지 곱해서 출력합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
 
int main() {
    int a,b,c,d,e;
    
    scanf("%1d%1d%1d%1d%1d",&a, &b, &c, &d, &e);
 
    printf("[%d]\n", a * 10000);
    printf("[%d]\n", b * 1000);
    printf("[%d]\n", c * 100);
    printf("[%d]\n", d * 10);
    printf("[%d]\n", e * 1);
    return 0;
}
cs

 

2차 풀이

입력 받는 정수는 5자리로 고정되어 있습니다.

그렇기 때문에 10,000으로 나누면 첫 번째 자리가 나오고 10,000을 곱해줍니다.

해당 값을 원래 값에서 빼주면 4자리 값이 나옵니다.

위 과정을 반복 해주면 문제를 해결할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
 
int main() {
    int a;
    int b = 10000;
    scanf("%d"&a);
 
    while (b > 0) {
        printf("[%d]\n", (a / b) * b);
        a -= (a / b) * b;
        b /= 10;
    }
    return 0;
}
cs

 

 

 

 

 

3번 1028 : [기초-데이터형] 정수 1개 입력받아 그대로 출력하기2

 

입력

정수 1개가 입력된다.
(단, 입력되는 정수의 범위는 0 ~ 4,294,967,295 이다.)

 

입력 예시

2147483648

 

출력

입력된 정수를 그대로 출력한다.

 

출력 예시

2147483648

 

풀이

1
2
3
4
5
6
7
8
9
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
 
int main() {
    unsigned int n;
    scanf("%u"&n);
    printf("%u", n);
    return 0;
}
cs

이번 문제를 통해 int의 범위는 "-2,147,483,648 ~ +2,147,483,647", unsigned int의 범위는 " 0 ~ 4,294,967,295 "인 것을 다시 한번 알 수 있었습니다.

 

 

 

 

 

4번 1029 : [기초-데이터형] 실수 1개 입력받아 그대로 출력하기2

 

입력

소수점 아래 숫자가 11개 이하인 실수 1개가 입력된다.
(단, 입력되는 실수의 범위는 +- 1.7*10^-308 ~ +- 1.7*10^308 이다.)

 

입력 예시

3.14159265359

 

출력

입력된 실수를 소수점 이하 11자리까지 반올림하여 출력한다.

참고
%.11lf 를 사용하면 소수점 이하 11자리까지 출력된다.

 

출력 예시

3.14159265359

 

풀이

1
2
3
4
5
6
7
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main() {
    double d;
    scanf("%lf"&d);
    printf("%.11lf", d);
}
cs

이번 문제를 통해 float 데이터형의 범위가 "+-3.4*10^-38 ~ +-3.4*10^38", double 데이터형의 범위가 "+- 1.7*10^-308 ~ +- 1.7*10^308"인 것을 다시 한번 알 수 있었습니다.

 

 

 

 

 

5번 1030 : [기초-데이터형] 정수 1개 입력받아 그대로 출력하기3

 

입력

정수 1개가 입력된다.
단, 입력되는 정수의 범위는 -9,223,372,036,854,775,808 ~ +9,223,372,036,854,775,807 이다.

 

입력 예시

-2147483649

 

출력

입력된 정수를 그대로 출력한다.

 

출력 예시

-2147483649

 

풀이

1
2
3
4
5
6
7
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main() {
    long long int n;
    scanf("%lld"&n);
    printf("%lld", n);
}
cs

int에도 long long int 데이터형이 존재한다는 사실을 이때까지 모르고 있었습니다. 또한 해당 데이터 타입이 표현할 수 있는 수의 범위가 "-9,223,372,036,854,775,808 ~ +9,223,372,036,854,775,807"인 것을 알게 된 문제였습니다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

참고

https://dojang.io/mod/page/view.php?id=376

 

C 언어 코딩 도장: 45.1 문자를 기준으로 문자열 자르기

45 문자열 자르기 지금까지 문자열을 복사하거나 붙이는 방법을 알아보았습니다. 이번에는 주어진 문자열을 자르는 방법을 알아보겠습니다. 참고로 문자열 자르기는 포인터를 이용하는 방식이

dojang.io

https://reakwon.tistory.com/64

 

[C언어] 문자열 함수2 그림으로 쉽게 보는 strtok, strstr 원리와 사용법, 예제

C언어 문자열 함수 문자열을 다룰때 어떤 문자열 단위로 자르고 싶은 경우나 어떤 문자열에서 임의의 문자열을 찾고 싶은 경우가 있지 않았나요? 그 경우에 사용할 수 있는 문자열 함수를 소개

reakwon.tistory.com

 

 

+ Recent posts