C (프로그래밍 언어)

Hedone21 (토론 | 기여)님의 2015년 10월 5일 (월) 01:53 판

C 프로그래밍 언어는 1972년에 데니스 리치에 의해 UNIX를 개발하기 위하여 만들어진 프로그래밍 언어이다. UNIX 시스템의 창시자라고 할 수 있는 켄 톰슨에 의하면, C언어와 UNIX를 만들고자 했던 결정적인 동기는 당시 그들이 몸담고 있던 벨 연구소의 직원들이 게임덕후였기 때문에 컴퓨터의 종류나 연식에 관계없이 자기들이 하던 게임을 돌리고자 했기 때문이라고 한다. [1]

특징

  • 절차적 프로그래밍

최근에 나온 함수형, 객체지향 프로그래밍 언어와는 달리 초기에 나온 절차적 프로그래밍 언어이다. 프로그래머에 따라 객체지향처럼 쓸 수도 있지만...[2]

  • 강 타입언어

C언어는 강 타입언어지만 부동소숫점 실수형과 정수형의 상호변환은 암시적으로도 가능하다.

  • 포인터를 이용한 저수준의 메모리 접근

C언어의 알파이자 오메가. C언어가 하드웨어에 가깝다고 평가받는 요소이다. 사람들이 C언어를 약 타입언어 같다고 느끼는 것도 포인터 때문인 경우가 많다.

  • 중괄호를 이용한 스코프 지정

기존의 프로그래밍 언어보다 C프로그래밍 언어를 사용한 코드가 간결해진 이유. 후대 언어에게도 영향을 미쳤다.

  • 컴파일 언어

현대(2000년도 이후)에 주류로 사용되는 프로그래밍 언어(Java, C#, Objective C, Python 등) 중 몇 안 되는 컴파일 언어이다.

장점

  • 사용자가 프로그램의 모든 부분을 관리할 수 있다.

모든 것을 사용자에게 맡긴다. 다른 프로그램이라면 컴파일 오류를 내뱉을 만한 구문이라도 C에서는 정상적으로 실행된다.

  • 익혀야 할 문법의 양이 굉장히 적다. 그리고 피해야 하는 함정들이 굉장히 많다.
  • 비트, 바이트 단위의 데이터 관리가 편리하다. 다른 언어들의 경우 비트, 바이트 단위의 데이터 접근에 상당한 제약이 있는 경우가 많다.
  • 이식성이 뛰어나다. 하드웨어가 바뀌고 OS가 바뀌어도, 해당 플랫폼의 컴파일러로 바꾸어 주고, 플렛폼 종속적인 코드만 재구현 하고 컴파일하면, 멀쩡하게 돌아간다. 절대 쉽다고는 안했다...
  • 다른 언어에 비하여 컴파일러의 제작이 비교적 쉬우며, 코드의 투명성이 높다. 그래서 새로 개발되는 프로그램 가능한 칩들은 가장먼저 C컴파일러부터 구현한다.

단점

  • 사용자가 프로그램의 모든 부분을 관리해야만 한다.

위에서 적은 장점은 곧 단점이기도 하다. 배열의 범위를 벗어나도 컴파일러는 오류를 내뱉지 않는다. 사용자가 메모리를 꼼꼼하게 관리하지 않으면 메모리 누수가 발생한다.

  • 문법은 단순하지만 언어 사용 기술은 단순하지 않다.

문법의 종류가 적다고 코딩이 쉬운 것은 아니다. 문법에서 지원하는 기능이 몇 개 없다는 건 바꿔 말하면 지원하지 않는 기능을 모두 손수 구현해서 사용한다는 뜻이 된다. 더불어 포인터와 관련되어 개발된 각종 트릭들은 C 언어를 본격적으로 사용할 때의 난이도를 대폭 상승시킨다.

버전

K & R

국제 표준이 나오기 이전의 C문법이다, C 개발자가 사용했던 문법이다.

ANSI C

국제 표준으로 등록된 문법 및 라이브러리며, 현재까지도 대다수의 C프로그래머들은 이 표준을 사용한다. C99,C11은 장식이다 소스코드 정적 분석을 하는 사람들도 대상 코드를 ANSI C로 잡는 경우가 대다수라서 앞으로도 대격변이 있지 않은 이상, 이 표준 이외의 표준이 주류가 되기는 어려울 것으로 보인다.

C99

그동안 발전이 뒤쳐져서 새로운 표준이 등록되었다. (ISO-IEC-9989) 진위형(bool)이나 인라인 함수, 변수 선언 제약의 완화등이 있으나, VC에서 지원하지 않아 잊혀졌다. gcc의 경우 c99 표준 명세를 대부분 지원하며, -std=c99 옵션을 통해 적용 가능하다.

ANSI C에비해 아래와 같은 기능이 추가되었다.

inline function 추가

inline int f(int n){n = n + 2; return n;}

인라인 함수는 C++에서는 일반적으로 사용하는 문법적으로 문법적으로는 함수의 형태를 띄고 있지만, 컴파일 할 때 콜스텍을 이용하여 호출하는 함수와 달리 인라인 함수의 내용을 해당 함수가 호출된 위치에 치환하는 형태로 동작한다. ANSI C에서는 이러한 기능을 제공하지 않아. 아래와 같은 매크로 함수를 사용하여 해당 기능을 사용했었다.

#define func(x) {int a=0; printf("%d\n",a);

그러나 이러한 방식의 매크로함수는 지정된 라인을 소스코드에 직접 치환하는 방식이기 때문에, 예상치 못한 에러를 유발할 가능성이 높아 사용이 까다롭다. 인라인 함수는 자신의 내용을 소스코드에 치환하는 것은 동일하나 코드 치환시 발생할 수 있는 문제에 대한 안전장치를 가지고 있다.

변수의 선언이 블록의 처음에 제한되지 않음

ANSI C의 경우 모든 변수는 반드시 블록의 처음부분 (다른 함수가 호출되기 이전)에 선언하지 않으면 컴파일 에러가 발생하였다. C99에서는 이러한 제한을 없애고, 블록의 어디에서든 변수를 선언하여 사용할 수 있도록 하였다.

int foo()
{
  printf("test");
  int a = 0;
  return a;
}

복소수를 나타내기 위한 complex 자료형 등 새로운 자료형 도입

아래와 같은 형태로 복소수의 표현과 연산이 가능하다.

#include <complex.h>  
int foo() 
{
   double complex x1 = 1.0 + 3.0 * I;
   double complex x2 = 1.0 - 4.0 * I;

   printf("x1 = %.2f + %.2fi\tx2 = %.2f %+.2fi\n", 
          creal(x1), 
          cimag(x1), 
          creal(x2), 
          cimag(x2));
}

가변 길이 배열(VLA: variable-length array)

ANSI C에서는 배열의 선언시 배열 길이를 반드시 상수로만 사용할 수 있었다. C99에서는 이러한 제한이 사라져 가변길이 배열의 선언이 가능하다.

int arr1[123]; //ansi C에서 정상동작
int length = 10;
int arr2[length]; // ansi C에서 에러 c99에서 정상동작

inttypes.h stdbool.h 와 같은 헤더 지원

inttypes.h : 플랫폼에 따라 사이즈가 달라지지 않는 자료형을 제공한다.
stdbool.h : 표준 bool 타입을 제공한다.

지정된 이니셜라이저 지원

배열의 초기화가 아래와 같이 이루어질 수 있다.

int a[6] = { [4] = 29, [2] = 15 }; 
int b[6] = { 0,0,15,0,29,0 };

가변인수 매크로

define 매크로 함수에 가변인자가 들어갈 수 있게 되었다.

#define FN(a,...) printf(a,_VA_ARGS_);

컴파운드리터럴

복합상수의 지정이 가능해졌다.

int *p = (int [3]) {1,2,3};

C11

C++기술위원회와 협의하여 C++11와 같이 나온 표준이다. 역시 C++11처럼 표준 스레드 라이브러리등이 추가 되었으나, 추가 되었는지도 모르는 사람이 태반이다.

문법

  • 위키문서에서 C언어의 모든 문법을 다루기는 어렵다. 그러니까 책을 사라 따라서 본문에서는 개발에 필수적인 항목만을 간단하게 다룬다.

변수

  • 데이터를 저장하기 위해 할당하는 메모리 공간이다. 할당과 해제를 컴파일러가 관리하기 때문에 개발자가 메모리 관리를 신경쓰지 않아도 된다.
  • 자료형은 아래와 같다.

char

  • 하나의 문자를 저장한다. 그러면 글자 여러개는 어떻게 저장하는데? 여러개 쓰면 되잖아
xxx = 'a';
  • short : 작은 정수를 저장한다.
short xxx = 3;
  • int : 정수를 저장한다.
int xxx = 4;
  • long : 큰 정수를 저장한다.
long xxx = 9;
  • float : 작은 실수를 저장한다.
float xxx = 3.141592;
  • double : 큰 실수를 저장한다.
double = 3.141592597;

함수

  • Ctrl+CV 대신 써야 하는거
  • 반복적으로 쓰이는 코드를 짧은 키워드로 불러 쓸 수 있도록 하는 기능제대로된 정의와는 일억오천만광년정도 떨어져 있다.
  • 인자를 가지고 특정 어드레스 주소로 이동할 수 있도록 하는 기능. 스택 추가는 덤.
void func(int a)
{
   printf("a : %d\n,"a);
}
 int main()
 {
   func(1);
   func(2);
   func(3);
   return 0;
 }

 출력
 a : 1
 a : 2
 a : 3

조건문

  • for
  • while
  • goto

if

  • 소괄호 안의 값을 확인하여 0이면 그냥 지나가고 0이 아니면, 블록 안의 코드를 실행하는 문법. 조건을 검사하는 기능같은거 없다.
  • 평범한 사용법
if(0)
{
  //실행 안된다.
}
if(2312)
{
 // 실행된다.
}
  • 비범한 사용법
int a = 10;
if(a - 10)
{
  //a가 10이 아니면 실행된다.
}
  • 좀 더 비범한 사용법
    • C에서 1 == 1 -> 1이고, 1 == 2 -> 0 이다.
    • 1 < 2 -> 1이고, 1 > 2 -> 0이다. 어떻게 되먹은 산수지...? [3]
int a = 10;
if(a < 11)
{
 //a가 11보다 작으면 실행된다.
}

삼항연산자

if/else 문과 같은 용도로 쓰인다. 간단한 조건문의 경우 짧은 코드로 작성할 수 있다는 장점이 있지만 반면 가독성이 떨어진다는 문제가 있기 때문에 상황에 맞게 조건문과 삼항연산자를 쓰는 것이 좋다.

  • 참이면 앞에거, 거짓이면 뒤에거..
  • 소괄호 안의 값을 확인하여 0이면 뒤에 것을 실행하고 0이 아니면, 앞에 것을 실행한다.
(1==2) ? 실행안됨 : 실행됨

사용자정의변수

C언어에서는 typedef 키워드로 사용자 변수 타입을 정의할 수 있다. 즉 typedef int bool; 을 이용해 int와 같은 성질을 갖는 자료형을 bool 자료형의 이름으로 사용 가능하다. 또한 여러 자료형과 변수를 한 단위로 묶기 위한 구조체와 공용체, 상수의 집합을 보다 알아보기 쉽게 정의할 수 있는 열거형 또한 사용자 정의 자료형으로 취급한다.

  • 구조체
struct xxx_t {
  int index;
  int age;
  int id;
};
int main()
{
  struct xxx_t x;
  x.index = 1;
  x.age = 2;
  x.id = 3;
  printf("%d %d %d\n",x.index,x.id,x.age);
  return 0;
}
  • 공용체

공용체와 구조체의 가장 큰 차이점은, 구조체는 각 속성들의 메모리 영역이 독립적으로 나뉘어져 있고 공용체는 모두 같은 공간을 공유한다는 점이다.

 union my_union{
   int age;
   char gender;
 };
 int main(){
   union my_union un;
   un.age = 20;
   printf("%d", un.age);
   un.gender = 'f';
   printf("%c", un.gender);
   printf("%d", un.age);
   return 0;
 }
  • 열거형

열거형은 위의 두 자료형과는 조금 다르며 정수 상수의 집합을 선언해 다음과 같이 프로그래밍 과정을 좀 더 쉽게 하기 위해 주로 사용된다. 예를 들면 다음과 같다.

 int main(){
   int day = 3;
   if(day == 0 || day == 6){
     printf("weekend!\n");
   }
   else{
     printf("weekdays!\n");
   }
   return 0;
 }

위와 같은 코드를 아래와 같이 바꿀 수 있다.

 enum daykind = { sunday = 0, monday, tuesday, wednesday, thursday, friday, saturday};
 int main(){
   enum daykind day = thursday;
    if(day == sunday|| day == saturday){
     printf("weekend!\n");
   }
   else{
     printf("weekdays!\n");
   }
   return 0;
 }

위의 예시보다 이해하고 읽기 쉬움을 알 수 있다.

포인터

포인터

다중포인터

함수포인터

매크로

각주

  1. 제작에 대한 비화
  2. 객체지향이라는 개념은 일부 프로그래밍 언어의 언어적 특징이기도 하지만, 기본적으로는 프로그래밍 패러다임의 한 종류이기 때문에 C와 같은 절차적 프로그래밍 언어로 비슷하게 구현하는 것이 가능하다. 대표적인 예로 gtk+ 라이브러리가 있다. c의 매크로 기능을 십분 활용하여 상속, 오브젝트 등을 구현하였다. 이 전 글에 리눅스의 VFS(Virtual File System)를 객체지향의 예라고 설명하였는데, 이는 모듈화의 좋은 예시이지 객체지향의 예는 아니다. 모듈화는 언어의 특징이라기 보다는 프로그래밍 방법론에 해당한다.
  3. 산수라기 보다는, 논리에 가깝다. 진실일때 1을 출력하고, 거짓일때 0을 출력한다고 가정하자, 1==2는 "1과 2가 같다"라는 말이므로 거짓이 된다. 그러므로 0을 출력한다. 반대로, 1==1은 1은 1이다라는 진실이므로 1을 출력한다. 그리고 1보다는 2가 크므로 진실, 1을 출력하고, 1이 2보다 큰것은 거짓이므로 0을 출력한다.