본문 바로가기
👩‍💻Developer/Language

[C] C언어에 대해 알아보자

by 블루누들 2022. 11. 25.

* 해당 글은 학과 수업으로 배운 내용과 코드가 포함되어 있으며 개인적 공부 목적으로 업로드하였습니다.

오늘은 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

댓글