* 해당 글은 학과 수업으로 배운 내용과 코드가 포함되어 있으며 개인적 공부 목적으로 업로드하였습니다.
오늘은 c언어에 대해 본격적으로 알아보겠다
여태까지 보아왔던 C0/C1은 contract을 포함한 C의 안전한(?) 버전이라고 할 수 있다.
C Preprocessor
일반적 C program은 2가지 언어로 작성된 statements로 구성되어있는데
-C preprocessor language(#으로 시작하는)
-C proper
이다.
C preprocessor는 모든 c preprocessor directives를 process하고
코드를 C proper로 나타내는 프로그램이다.
C preprocessor에 들어갈 C preprocessor directives의 종류에는 아래와 같다
-File inclusion
-Macro definitions
-Conditional compilation
-Macro functions
File Inclusion
파일 안에 있는 내용들을 현재 프로그램에 적용하고자 할때 사용된다.
-# include <stdio.h> : system file인 stdio.h를 현재 프로그램에 포함시킨다.
-#include "lib/xalloc.h": local file인 lib/xalloc.h를 현재 프로그램에 포함시킨다.
우리가 #include할 수 있는 것은 header file만 가능하다(일반적으로 .h로 끝나는 파일들이다)
header file는 library interace와 다른 preprocessor directive를 포함시키고 있다.
Marco definitions
macro definition은 constant에 특정 이름을 부여하는 방법이다.
ex)#define INT_MIN 0x80000000
이후 프로그램은 macro symbol INT_MIN을 사용할 수 있으며 preprocessor는 매번 INT_MIN이 사용될때마다 0x80000000로 replace할 것이다.
관념적으로 macro symbol은 모두 대문자로 쓰이며 constants에 이름을 주기에 좋은 방법이다.
constants 뿐만 아니라 expression을 정의할때도 사용할 수 있다.
ex) #define INT_MAX INT_MIN-1
이때 만약 INT_MAX /2 를 preprocessor를 통해 계산한다면
이것은 0x80000000-1/2로 계산될것인데 C언어에서는 이것이
0x80000000-(1/2)로 계산되기에 원하는 결과를 얻을 수 없다.
C preprocessor는 순서를 제대로 인지하지 못하기 때문에 먼저 인식되길 원하는 부분에 괄호를 추가시켜주는 것이 필요하다
#define INT_MAX( INT_MIN-1)
이럴 경우 INT_MAX /2 를 preprocessor를 통해 계산한다면
(0x80000000-1)/2로 계산될 것이다.
Conditional Compilation
program segment를 특정 symbol이 정의되었는지 아닌지에 따라 compile 여부를 다르게 만들 수 있다.
#ifdef DEBUG //만약 DEBUG가 define되었다면
printf("Reached this point\n");
#endif
DEBUG는 #define DEBUG의 command로 정의할 수 있다.
또한 ifelse처럼 #else clause를 추가할 수 도 있다.
#ifdef X86_ARCH
#include "arch/x86_optimizations.h"
x86_optimize(code);
#else
generic_optimize(code);
#endif
또한 symbol이 define되었는지 아닌지도 확인할 수 있다.
#ifndef INT_MIN //define되지 않았다면 define해라
#define INT_MIN 0x80000000
#endif
또한 macro defintion을 arguments를 포함시켜 정의할 수 있다.
ex) #define MULT(x,y) x*y
이때 MULT(1+2, 3-5)/2를 preprocessor를 통해서 계산한다면
1+2 * 3-5 /2로 계산되므로 원하는 방식이 아니다.
역시나 이때에도 우리는 괄호를 추가해야한다.
#define MULT(x,y) ((x)*(y))
Contracts Emulation
C에는 contract이 존재하지 않는다.
하지만 contracts.h파일을 가져온다면 macro function을 활용할 수 있다.
-REQUIRES(condition)
-ENSURES(condition)
-ASSERT(condition)
contracts.h파일에는 IF_DEBUG(cmd) 라는 macro function도 존재한다.
-이는 DEBUG라는 symbol이 정의되었을때 cmd가 execute된다
print statement를 통해 code debugging할때 유용하게 사용할 수 있다.
IF_DEBUG(printf("Reached this point\n"));
Translating a Library to C
function implementation을 C언어로 translate해보자
/***Implementation***/
#include "simple.h" //absval 함수는 simple.h header file에 포함되어있다.
int absval(int x)
//@requires x > int_min();
//@ensures \result >=0;
{
return x < 0 ? -x : x;
}
위의 함수에서 먼저 우리가 바꿔야할 것은 precondition이다.
//@requires -> REQUIRES가 된다
그리고 이 REQUIRES는 contracts.h header file에 존재하게 된다.
이때 int_min()은 C에 미리 정의되어있지 않기 때문에 limits.h header file에 존재하는INT_MIN symbol을 활용한다.
/***Implementation***/
#include "simple.h" //absval 함수는 simple.h header file에 포함되어있다.
#include "lib/contracts.h"
#include <limits.h>
int absval(int x)
//@requires x > int_min();
//@ensures \result >=0;
{
REQUIRES(x > INT_MIN);
return x < 0 ? -x : x;
}
다음은 똑같이 postcondition을 바꾼다. 단 postcondition은 모든 return statement직전에 매번 나와야한다.
또한 우리가 return하는 것이 복잡한 expression이기 때문에 이것을 임시 변수에 저장하여 return해야 postcondition에 활용가능하다
/***Implementation***/
#include "simple.h" //absval 함수는 simple.h header file에 포함되어있다.
#include "lib/contracts.h"
#include <limits.h>
int absval(int x)
//@requires x > int_min();
//@ensures \result >=0;
{
REQUIRES(x > INT_MIN);
int result = x < 0 ? -x : x;
ENSURES(result >=0);
return result;
}
Translating a Program File to C
이제는 client library를 translate해보자
예시로 아래 파일이 존재한다고 가정해보자
int main(){
struct point2d* P = alloc(struct point2d);
P->x = 15;
P->y = P->y + absval(P->x*2);
assert(P->y > P->x && true);
print("x coord: "); printint(P->x); print("\n");
print("y coord: "); printint(p->y); print("\n");
return 0;
}
input/output system 함수들은 stdio.h header file에 존재한다.
또한 C 에 memory를 allocate하는 방법은 다르다.
malloc을 call해야한다.
이때 malloc은 size을 input으로 받으며 이것은 allocate할 bytes를 의미하며 sizeof 함수를 통해 해당 type이 가지는 bytes를 allocate할 수 있게 된다.
#include <stdio.h>
#include <stdlib.h>
int main(){
struct point2d *P = malloc(sizeof(struct point2d));
이때 malloc을 사용할 경우 충분한 memory가 없을시 NULL을 return하게 된다.
unsafe할 수 있기에 xalloc.h library의 xmalloc을 활용하는 것이 중요하다
#include <stdio.h>
#include <stdlib.h>
#include "lib/xalloc.h"
int main(){
struct point2d *P = xmalloc(sizeof(struct point2d));
P->x = 15;
P->y = P->y + absval(P->x*2);
assert의 경우 assert.h header file에 존재하지만 print 함수는 존재하지 않는다.
따라서 이는 stdio.h 에 정의된 printf함수로 대체할 수 있다.
printf함수는 format string을 활용하기 때문에 placeholder가 필요하고 이것들이 추가 arguments로 활용될것이다.
#include <stdio.h>
#include <stdlib.h>
#include "lib/xalloc.h"
#include <assert.h>
int main(){
struct point2d *P = xmalloc(sizeof(struct point2d));
P->x = 15;
P->y = P->y + absval(P->x*2);
assert(P->y > P->x && true);
printf("x coord: %d\n", P->x);
printf("y coord: %d\n", P->y);
return 0;
}
Compiling a C Program
C program을 compile할 때 아래와 같은 code 로 compile할 수 있다.
#gcc -Wall -Wextra -Wshadow -Werror -std=c99 -pedantic -g -DDEBUG lib/*.c simple.c test.c
header file을 include했기 때문에 c file만을 compile한다.
Separate Compilation
여러 c0 file들을 compile할때 그들은 하나의 file로 compile된다.
반면 C에서는 각 file들은 각자 compile되며 a.out을 통해 합쳐져서 나오게 된다.
또한 여러번 header file이 include될 경우 struct가 여러번 정의되면서 에러를 불러올 수 있다.
또한 conditional compilation을 통해서 definition이 한번만 정의되도록 한다.
예를 들어
#ifndef SIMPLE_H
#define SIMPLE_H
int absval(int x)
/*@requires x > int_min(); @*/
/*@ensures \result >=0; @*/
struct point2d{
int x;
int y;
};
#endif
이 위에서 SIMPLE_H가 정의되어있지 않을 경우 정의를 하고
정의가 되어 있다면 아무런 추가 액션을 하지 않도록 한다.
Booleans
boolean은 C언어에서 primitive type이 아니다. 따라서 boolean을 사용하기 위해서 우리는
<stdbool.h>를 포함시켜야 한다.
#include <stdbool.h>
우리가 특정 파일을 compile했을 때 C는 allocated memory 에 default value를 initialize를 하지 않는다.
따라서 compile할때마다 다른 결과값을 야기할 수 있다.
따라서 우리는 이때 valgrind를 사용해야 한다.
valgrind ./a.out를 통해서 확인할 수 있다.
Memory Leaks
#include <stdio.h>
#include <stdlib.h>
#include "lib/xalloc.h"
#include <assert.h>
int main(){
struct point2d *P = xmalloc(sizeof(struct point2d));
P->x = 15;
P->y = P->y + absval(P->x*2);
assert(P->y > P->x && true);
printf("x coord: %d\n", P->x);
printf("y coord: %d\n", P->y);
return 0;
}
위 코드를 valgrind로 compile했을 때 8 bytes가 여전히 사용중임을 확인할 수 있다.
C0이라면 garbage-collected될것이지만 C에서는 이 allocated memory를 직접 free 해야한다.
프로그램은 사용하지 않는 memory가 free되지 않는다면 memory leak이 발생한다.
따라서 C언어를 사용할때는 항상 "allocate했다면 free해야한다"라는 말을 생각해야한다.
따라서 위의 코드에서 아래와 같이 free할 수 있다.
#include <stdio.h>
#include <stdlib.h>
#include "lib/xalloc.h"
#include <assert.h>
int main(){
struct point2d *P = xmalloc(sizeof(struct point2d));
P->x = 15;
P->y = P->y + absval(P->x*2);
assert(P->y > P->x && true);
printf("x coord: %d\n", P->x);
printf("y coord: %d\n", P->y);
free(P);
return 0;
}
free(P)는 여기서 어떤 역할을 하는 것인가?
-이것은 P에 point되었던 memory를 다시 컴퓨터에 돌려주는 것이다.
이때 P는 여전히 똑같은 주소값을 가질 것이고 P에는 다른 값들을 assign할 수 있다.
하지만 이때 우리는 아직 사용해야할 memory를 free해서는 안된다.또한 한번 free한 memory를 또다시 free할 수 없다.
Data Structure Libraries in C
Data strcture도 메모리를 allocate한다. 따라서 이 memory또한 free되어야 한다.
따라서 interface안에도 free할 수 있는 함수가 필요하다.
이때 data structure안에 있는 data 도 free할지 안할지 결정할 필요가 있다.
따라서 client가 free할지 안할지 결정하도록 해야한다.
이는 즉 library가 data에 의해 사용된 memory가 누구에 의해 own되는지를 결정해야 한다.
library에 dict_free라는 함수를 정의할 수 있는데 이때 이것이 client interface에서 정의된 free함수를 input으로 받게 된다.
만약 이 함수가 NULL이 아니라면 안의 이 함수가 free하는 data를 free할 수 있다.
예를 들어 struct이 트리구조라면
void tree_free(tree*T, entry_free_fn *Fr){
REQUIRES(is_bst(T));
if (T==NULL) return;
if (Fr!=NULL) (*Fr)(T->data);
tree_free(T->left, Fr);
tree_free(T->right, Fr);
free(T);
}
void dict_free(dict*D, entry_free_fn *Fr){
REQUIRES(is_dict(D));
tree_free(D->root, Fr);
free(D);
}
'👩💻Developer > Language' 카테고리의 다른 글
[C] Search in Graph (0) | 2022.12.16 |
---|---|
[C] Graph In C (0) | 2022.12.15 |
[C]Bytecode In C (1) | 2022.12.15 |
[C] C에서 숫자란? (0) | 2022.12.14 |
[C/C++] C언어의 Memory model (1) | 2022.11.29 |
댓글