C언어/문법

최근 수정 시각: (5년 전)
목차
1. 개요2. 기본3. 편집 지침4. 컴파일 과정 및 문법적 요소들
4.1. 어휘 요소(lexical elements)
4.1.1. 키워드(keyword)4.1.2. 식별자(identifier)4.1.3. 상수(constant)4.1.4. 문자열 상수(string-literal)4.1.5. 구두점(punctuator)
5. 수식(expression)
5.1. 산술 연산자5.2. 할당 연산자5.3. 증감 연산자5.4. 비트 연산자5.5. 비교 연산자5.6. 3항 연산자
6. 변수, 자료형
6.1. 기본 데이터 모델6.2. 변수 선언6.3. 변수 값 변경6.4. 정수 자료형
6.4.1. char, signed char, unsigned char6.4.2. short
6.4.2.1. 예시
6.4.3. int
6.4.3.1. 예시
6.4.4. long (int)6.4.5. long long (int)6.4.6. __int128
6.5. 문자 자료형
6.5.1. char
6.5.1.1. 초기화 방법6.5.1.2. 인코딩 방식
6.5.1.2.1. ASCII 인코딩6.5.1.2.2. UTF-8 인코딩
6.5.1.3. 예제
6.5.2. wchar_t6.5.3. TCHAR6.5.4. char16_t6.5.5. char32_t
6.6. 실수 자료형
6.6.1. float6.6.2. double6.6.3. long double
6.7. void6.8. 배열6.9. 포인터
6.9.1. 예시6.9.2. 함수 포인터6.9.3. 복잡한 선언문 읽기6.9.4. 동적 할당
6.10. 형변환
7. 함수
7.1. main 함수7.2. 내장 연산자
7.2.1. sizeof
7.2.1.1. 예시
7.3. 헤더 파일7.4. 함수 일람
7.4.1. 입/출력 함수
7.4.1.1. printf
7.4.1.1.1. 기본 문법7.4.1.1.2. 리터럴7.4.1.1.3. 서식 지정자
7.4.1.2. scanf
7.4.1.2.1. 서식 지정자
8. 반복문
8.1. for8.2. while8.3. do-while
9. 무조건 분기문
9.1. goto9.2. break9.3. continue9.4. return
10. 조건 분기문
10.1. if [else]10.2. switch ... case ...
11. 관련 문서

1. 개요 [편집]

C언어의 문법을 설명하는 문서다.

2. 기본 [편집]

기본적으로 C언어의 코드의 구조는 다음과 같다.
[헤더 파일 include]
[함수 선언]
[main 함수]
이 중, 헤더 파일 include는 쉽게 설명해서, C언어가 가지고 있는 함수를 불러오는 부분이다. 함수 선언은 자신이 만든 함수를 등록하는 부분, main 함수는 함수, 즉 명령을 그 안에서 처리한다.

3. 편집 지침 [편집]

소스 코드로 예시를 들 때
{{{#!syntax cpp (소스코드) }}}
문법을 활용하여 소스코드를 써주시기 바랍니다.

예시:
#include <stdio.h>
int main(void)
{
     printf("hello world!\n");
     return 0;
}

4. 컴파일 과정 및 문법적 요소들 [편집]


컴파일러는 일단 어휘 분석과 구문 해석 단계를 거친다. 비유를 하자면 영어 문장을 단어별로 끊는게 어휘 분석이고, 그 끊어진 단어들을 문법에 맞게 동사/명사/전치사 등으로 나누어 해석하는게 구문 해석 단계이다.

C언어에서, 어휘 분석을 거친 뒤에 나오는 어휘 요소(lexical element)의 종류는 다음과 같다.
  • 키워드(keyword)[1]
  • 식별자(identifier)
  • 상수(constant)
  • 문자열 상수(string-literal)
  • 구두점(punctuator)

구문 해석을 거친 뒤에 토큰이 모여 이루어진 각 구절들은 구조와 의미에 따라 다음과 같이 해석된다.
  • 수식(Expression)
  • 문장(Statement)
  • 선언(Declaration)
  • 정의(Definition) (엄밀히는 함수 정의(function-definition))

이 4가지 사이에는 포함관계가 성립된다. 외부 정의(external-definition)에 선언과 함수 정의(function-definition)가 포함되며, 함수 정의 끄트마리에 문장(정확히는 compound-statement)이 포함되고 문장에 수식이 포함된다.

4.1. 어휘 요소(lexical elements) [편집]

4.1.1. 키워드(keyword) [편집]


C99 기준으로 다음의 37개가 예약되어 있으며, 다른 용도로는 사용되지 못한다.

auto break case char const
continue default do double else
enum extern float for goto
if inline int long register
restrict return short signed sizeof
static struct switch typedef union
unsigned void volatile while _Bool
_Complex _Imaginary

4.1.2. 식별자(identifier) [편집]


식별자는 영어 대소문자, 숫자, 밑줄문자(underscore)로 이루어져야 한다. 대소문자는 서로 구분된다. 식별자의 길이에 특별한 제한은 없으나 실질적으로는 컴파일러에 따라 한계치가 정해진다.

미리 정해진 식별자로 __func__ 가 있다. 이는 각 함수 내부에서 다음과 같이 취급된다.


static const char __func__[] = "function-name";


이를 이용하면 각 함수 내부에서 함수의 이름을 문자열로 얻거나 출력할 수 있다.

4.1.3. 상수(constant) [편집]


상수에는 다음의 4가지가 존재한다.
  • 정수 상수(integer-constant)
  • 부동형 상수(floating-constant)
  • 열거형 상수(enumeration-constant)
  • 문자 상수(character-constant)

4.1.4. 문자열 상수(string-literal) [편집]



4.1.5. 구두점(punctuator) [편집]


독립된 의미를 갖는 심볼들을 뜻한다. C99 기준으로 다음과 같은 것들이 있다.

[ ] ( ) { } . ->
++ -- & * + - ~ !
/ % << >> < > <= >= == != ^ | && ||
? : ; ...
= *= /= %= += -= <<= >>= &= ^= |=
, # ##
<: :> <% %> %: %:%:

5. 수식(expression) [편집]

수식은 연산자(operator)와 피연산자(operand)들의 나열로서, 값을 계산하거나 객체 또는 함수를 지정하거나, 여러 부수효과(side effect)들을 일으키는 역할을 한다.

연산자에는 다음과 같은 것들이 있다.
  • Postfix operator
  • Unary operator
  • Cast operator
  • Multiplicative operator
  • Additive operator
  • Bitwise shift operator
  • Relational operator
  • Equality operator
  • Bitwise AND operator
  • Bitwise exclusiive OR operator
  • Bitwise inclusive OR operator
  • Logical AND operator
  • Logical OR operator
  • Conditional operator
  • Assignment operator
  • Comma operator

위로 갈수록 우선순위가 높다.

5.1. 산술 연산자 [편집]

연산자
+
덧셈 또는 양의 단항 연산자
-
뺄셈 또는 음의 단항 연산자
*
/
%

5.2. 할당 연산자 [편집]

변수에 자료를 할당할때 쓰이는 연산자들. 가장 기본적으로 =가 있고 편의를 위해서 다른 이항 연산자 옆에 =를 붙인것들이 있다. 예를 들면 x *= 3x = x * 3와 같다. 또한 할당 연산자가 사용된 식의 값은 대입된 값과 같다. x가 1이라면 x *= 3의 값은 3이다. 따라서 a = (b *= 3) * 2와 같은 표현이 가능하다. b가 1이었다면 연산 후에 b는 3, a는 6이 된다. 여러 변수를 초기화할 때 a = b = c = d = 0과 같이 사용할 수 있다. 코드를 줄여 쓰기 좋아하는 사람들이라면 while( a = func() ) 처럼 사용하기도 하는데 func()의 리턴값을 a에 대입하고 0이 아닌 동안 반복한다. 문법상 오류가 아니기 때문에 주의해야 한다. 최신 컴파일러들은 논리식이 와야 할 자리에 할당식이 나오면 경고를 표시해 주기도 한다.

5.3. 증감 연산자 [편집]

자료에 1을 더하거나 빼주는 단항 연산자들. 피연산자인 자료형의 왼쪽에 쓰냐 오른쪽에 쓰냐에 따라 의미가 살짝 다르다.
연산자
예시
++ (전위)
++a
자료에서 1을 더하고 반환
++ (후위)
a++
자료를 반환하고 1을 더하기
(전위)|| {{{a}}}
자료에서 1을 빼고 반환
(후위)|| {{{a}}}
자료를 반환하고 1을 빼기

전위와 후위 증감 연산자의 차이점을 보여주는 코드:
int a = 3;
int b = 3;
int x = ++a;
int y = b++;

이제 ab는 둘 다 4다. x4지만 y는 아직 3이다.

++a와 a++ 사이에 성능 차이가 있다는 소문이 있으나, 이는 C 컴파일러의 최적화가 미숙했던 오래전 이야기이고, 요새 기준으로는 별 의미없는 이야기이다. 어차피 C언어에서는 시퀀스 포인트 지점이 되기 전까지만 객체를 변경하면 되기 때문에 수식 내부에서 연산자를 만나는 즉시 즉각적으로 객체가 변경되리라고 (따라서 전위 연산자가 후위 연산자보다 더 효율적이라고) 믿는 것 부터가 심각한 오류이다. 한 덩어리(엄밀히 말하자면 두 시퀀스 포인트 사이)의 수식 안에서는 객체의 값을 캐시와 레지스터로 복사하여 처리하고, 실제 객체의 변경은 시퀀스 포인트가 나오는 지점까지 미뤄질 수 있다는(코드의 맥락과 최적화 정도에 따라서는 그 훨씬 뒤로도!) 이야기이다. 그렇다면 전위 연산자든 후위 연산자든 큰 의미는 없다. C++ 기준으로는 연산자 오버로딩이나 r-value값의 생성 등으로 의미있는 차이가 생길수 있으나 C와는 거리가 먼 얘기이다.

5.4. 비트 연산자 [편집]

말그대로 비트 연산자들로, AND OR XOR NOT 등이 있다. 이 연산자들 자체에 대해선 논리 연산으로.
  • &: AND
  • |: OR
  • ^: XOR
  • ~: NOT
  • >>: 비트를 오른쪽으로 이동
  • <<: 비트를 왼쪽으로 이동



참고로 여기다가 옆에 =를 붙이면 어떤 연산을하고 원래 값에 대입하라는 뜻. 예를 들어서 x >>= 2x = x >> 2와 같다.
int변수를 생성할때 값을 2진수로 쓰고 싶다면 앞에 0b를 붙이면 된다. 예를 들면 int n = 5int n = 0b101과 같다.

사용 예시 1: 값이 짝수인지 홀수인지 구별해서 출력해주는 함수
[펼치기]
#include <stdio.h>

void oddOrEven(int n){
    if (n & 1){
        printf("홀수\n");
    }
    else {
        printf("짝수\n");
    }
}

사용 예시 2: 정수를 2진수 (32비트)로 바꿔서 출력하는 함수
[펼치기]
#include <stdio.h>

void printBit(int n){
    for (int i = 1 << 7; i >= 1; i >>= 1){
    if (n & i){
      printf("1");
    }
    else {
      printf("0");
    }
  }
  printf("\n");
}

사용 예시 3: if문 없이 최솟값/최댓값 반환하기[참고][참고2]
[펼치기]
최솟값
int min(int a, int b){
    return b ^ ((a ^ b) & -(a < b));
}

최댓값
int max(int a, int b){
    return a ^ ((a ^ b) & -(a < b));
}

비트를 열거형으로써 사용할 수도 있는데, 이것들을 비트 연산자들로 조작이 가능하다. 예를 들어서 어떤 프로그램을 만드는데 파랑, 초록, 노랑, 빨간 불빛이 나오는걸 컨트롤 하고싶다고 치자. 어떤 불이 켜져있는지의 상태를 플래깅 하기 위해서는 1개의 정수면 충분하다.
enum Lights {
  GREEN = 1 << 0, //0b1
  YELLOW = 1 << 1, //0b10
  RED = 1 << 2, //0b100
};
int main(void){
  int flag = 0; //모두 꺼져있는 상태
  flag |= (RED | GREEN); //빨간불과 초록불 켜기
  flag &= ~(RED | GREEN); //빨간불과 초록불 끄기
  flag = ~0; //모든 불 켜기
  flag = 0; //모든 불 끄기
  flag ^= RED; //빨간불의 상태를 바꾸기
  flag = ~flag; //모든 불들의 상태를 바꾸기
  if ((flag & (RED | GREEN)) == (RED | GREEN)){
    //빨간불과 초록불이 동시에 켜져있는지 확인
  }
  if (flag & (RED | GREEN)){
    //빨간불과 초록불 둘 중 하나라도 켜져있는지 확인
  }
//...etc.

보다시피 할 수 있는게 아주 많다. 숫자 1개로만 이것들을 다 할 수 있으니까 빠르고 메모리 효율도 좋다.

5.5. 비교 연산자 [편집]

두 수식의 값을 비교한다.
a == b: a와 b가 같으면 참(non-zero), 다르면 거짓(0)이다.
a != b: a와 b가 같으면 거짓, 다르면 참이다.
a < b, a <= b: a가 b보다 작으면/작거나 같으면 참, 아니면 거짓이다.
a > b, a >= b: a가 b보다 크면/크거나 같으면 참, 아니면 거짓이다.

참고로 포인터 변수끼리의 크기 비교는 같은 타입이라고 해도 정의되지 않은(unspecified) 사항들이 많다. 해괴한 버그를 피하려면 포인터끼리는 ==, !=만을 사용하는 쪽이 좋다.

5.6. 3항 연산자 [편집]

a? b:c의 형태로 a값이 참(non-zero)이면 b, 거짓이면 c이다. a = (b == 10)? 20:30이라고 쓴다면 b가 10일 때 a에는 20이, 그 이외에는 30이 대입된다.

6. 변수, 자료형 [편집]

C언어의 변수에는 자료형이라는 게 있다. 변수를 상자에 비유한다면 자료형이란 상자의 모양이 된다. 상자에 넣을 내용물에 따라 상자의 모양을 결정하듯이, C언어의 자료형은 변수에 넣을 내용물에 따라 결정된다.

C언어에는 Standard signed integer types라 하여 5가지 기본 (부호 있는) 정수형이 존재한다.
  • signed char
  • short int
  • int
  • long int
  • long long int
이 다섯 가지는 서로 랭크가 다르며, 아래로 갈수록 랭크가 높다. 랭크가 높으면 표현 범위가 더 크거나 같다.

정수형의 크기는 컴파일러(좀 더 정확히는 사용하는 환경 자체를 가리켜 구현체(implementation)라는 용어를 사용한다)에 따라 다르다. 그러나 char<=short<=int<=long<=long long의 표현범위 순서는 반드시 지켜야 하며, 그 최소 표현범위도 아래와 같이 정해져 있다(즉 아래에 명시된 것보다는 더 넓은 범위를 표현 가능해야 한다).

* char: -127~127 (적어도 8비트 이상)
* short: -32,767~32,767 (적어도 16비트 이상)
* int: −32,767~32,767 (적어도 16비트 이상)
* long: −2,147,483,647~2,147,483,647 (적어도 32비트 이상)
* long long: −9,223,372,036,854,775,807~+9,223,372,036,854,775,807 (적어도 64비트 이상)

위에 제시된 C표준의 최소 표현 범위는 1의 보수(ones' complement) 표기법과 부호-크기(sign-magnitude) 표기법을 염두에 두고 정해진 범위이기 때문에, 우리가 주로 쓰는 2의 보수(two's complement) 표기법의 컴퓨터(그리고 그 위에서 돌아가는 컴파일러)와는 그 범위가 약간 다르다. n비트 정수형의 경우 일반적인 2의 보수 표기법의 컴퓨터에서 -(2^(n-1)) ~ (2^(n-1) - 1) 범위의 정수를 표현할 수 있다면, C 표준에서 요구하는 정수형의 최소 표현 범위는 -(2^(n-1) - 1) ~ (2^(n-1) - 1)로 그보다 1이 더 작게 되며, 이와 관련하여 negative zero와 같이 0의 또다른 표기방식이 생기게 된다. 자세한 내용은 위키백과의 Signed zero 항목 참조.

또한 C언어에는 Real floating type이라 하여 3종류의 부동소수형이 존재한다.
  • float
  • double
  • long double

이 세 가지는 위의 것이 아래의 것의 부분집합(subset)이어야 한다.

C언어에서 char, signed char, unsigned char형은 문자형(character type)이라 분류한다. char형은 signed char형과 unsigned char형 중에서 하나와 동일한 표현 범위, 동작을 갖는다. (그러나 그렇다고 해서 동일한 타입이 되는 건 아니다)

정수형, 부동소수형, 문자형의 세 가지 타입을 통틀어 기본형(basic type)이라 부른다.

6.1. 기본 데이터 모델 [편집]


C언어는 기본적으로 다양한 컴퓨터 환경과 구현체(implementation)를 염두에 두고 만들어졌기 때문에, 각각의 데이터 형들의 크기 또한 각 구현체가 선택 가능하도록 하고 있다. 일반적으로 널리 쓰이는 컴퓨터와 OS 위에서의 데이터 모델의 종류와 범위는 다음과 같다[4]
Datatype
LP64
ILP64
LLP64
ILP32
LP32
char
8
8
8
8
8
short
16
16
16
16
16
_int32
32
32
32
32
32
int
32
64
32
32
16
long
64
64
32
32
32
long long
64
64
64
64
64
pointer
64
64
64
32
32

위의 표에서 숫자는 메모리공간에서 차지하는 비트(bit) 수를 의미한다. 위의 LP, ILP 등의 명칭들은 C언어 표준에 직접적으로 명시된 이름들은 아니며, 관용적으로 쓰이는 명칭들이다. C표준에서 정한 최소 표현 범위와 그 순서만 지킨다면, 위에 서술한 것 외의 여러가지 다양한 데이터 모델이 사용될 수 있다.

주요 컴퓨팅 환경에서 사용하는 메모리 모델은 다음과 같다[5].

* ILP32: 32비트 윈도를 포함한 대부분의 32비트 시스템
* LP32: 윈도 3.1의 win-16 API, 애플 매킨토시
* LLP64: 64비트 윈도
* LP64: 64비트 리눅스

6.2. 변수 선언 [편집]

자료형 변수이름;

예시:
int a; // 정수형 변수 a를 선언한다.

a라는 변수를 int라는 자료형으로 선언한다. 이때 갓 선언된 변수에는 보통 의미가 없는 쓰레기 값(garbage value)이 들어 있으며, 일부 IDE에서는 쓰레기 값이 들어있는 변수를 쓰려고 하면 오류를 내기도 한다.

6.3. 변수 값 변경 [편집]

변수에 값을 대입하는 법은 다음과 같다.
변수의 이름 = 넣을 값;

#include <stdio.h>

int main()
{
	int a;
	a = 10;
	return 0;
}


다음과 같이 초기화와 대입을 한 번에 할 수도 있다.
자료형 변수의 이름 = 넣을 값;

#include <stdio.h>

int main()
{
	int a = 10;
	return 0;
}

6.4. 정수 자료형 [편집]

정수만 들어갈 수 있다. 만약 실수가 들어갈 시 소수점 뒤 부분은 사라지고 앞부분만 들어간다.
#include <stdio.h>

int main()
{
	int a;
	a = 5.8;
	printf("%d", a);
	return 0;
}

따라서 이 프로그램의 출력 결과는 5이다.

6.4.1. char, signed char, unsigned char [편집]


char형은 정수형이면서 문자형이다. '문자'를 정수형으로 처리하는 C언어의 특성 때문이다.

C언어에서 char형은 무조건 1byte여야 한다. 즉 sizeof(char)의 값은 항상 1이다. 그러나 C언어 표준 상으로는 1byte>=8bits이며, 그리고 모든 메모리 공간은 1byte단위로 주소 지정이 가능해야 한다. 특정 아키텍쳐(예를 들자면 DSP) 같은 곳에서는 1byte=16bit일 수도 있다.

실제 하드웨어와 C언어 구현체(implementation)와의 동작에는 다소 차이가 있을 수도 있다. 예를 들어 1byte=16bit인 아키텍쳐라도 소프트웨어적인 추상화를 통해 1byte=8bit 인 것처럼 언어적인 환경을 제공할 수도 있다. 혹은 그 반대도 가능하다. 이런 경우에는 pointer to char의 크기가 하드웨어상의 word의 크기와 달라질 수도 있다(해당 워드상의 몇 번째 비트인지를 표시해주는 부분이 추가되어야 하기 때문이다).

실행환경에서의 basic character set은 무조건 1byte 안에 들어가야 한다. 이 덕분에 어느 정도는 ASCII나 EBCDIC, UTF-8 등을 강제당하는 면이 있다. UCS-2나 UCS-4를 basic character set으로 갖는 C컴파일러를 만들려면 byte의 크기부터 커져야 하고, 메모리 주소 계산하는 방법도 달라져야 한다. [6]

6.4.2. short [편집]

가장 짧은 정수 자료형. 쓰는 일은 거의 없다. 컴퓨터는 몇 비트씩 묶은 WORD를 기준으로 자료처리를 하는데, short는 기준이 되는 WORD보다도 짧아서 처리 속도가 느려지기 때문이다. 메모리든 하드든 저장 공간 절약이 중요하던 시절에는 short도 많이 썼었다.

최소 2바이트 정수이고 이때 signed는 최소한 -32767~32767, unsigned는 최소한 0~65535까지 저장이 가능하다.
6.4.2.1. 예시 [편집]
#include <stdio.h>

int main()
{
	short a;
	a = 32768;
	printf("%d\n", a);
	return 0;
}

이 프로그램의 출력결과는 32768이 아니다. 그 이유는 a라는 변수는 자료형이 short이기 때문에 32767까지 저장이 되어서 값이 제대로 저장이 되지 않기 때문이다.

#include <stdio.h>

int main()
{
	short a;
	a = 33;
	printf("%d\n", sizeof(a));
	return 0;
}

이 프로그램의 출력결과는 short 자료형의 바이트 수인 2이다.

6.4.3. int [편집]

변수 선언 빈도수 1위. 제일 많이 쓰인다. 보통 int를 시스템의 WORD와 같게 맞추기 때문에 속도가 잘 나온다. short는 너무 사이즈가 작고 long long int는 ~너무 길어서(?)~[7] 단, C언어 표준상의 규정 때문에 8bit 컴퓨터라 하더라도 int형은 16비트 이상이어야 한다.

위에서 말한 대로 그 최소 표현 범위는 −32,767~32,767(-(2^15 - 1) ~ (2^15 - 1))이며, 컴파일러와 OS의 정수형 메모리 모델에 따라 굉장히 다양한 표현범위가 가능하므로, 자신이 사용하는 환경에서의 범위를 미리 확인해 두는 것이 좋다. 이식성을 고려한다면, 이러한 것들을 염두에 두고 코드를 작성해야 한다.
6.4.3.1. 예시 [편집]
#include <stdio.h>

int main()
{
	int a;
	a = 33;
	printf("%d\n", sizeof(a));
	return 0;
}

출력 결과는 4이다.

6.4.4. long (int) [편집]


long int 또는 줄여서 long형이라고 쓴다. 최소한 [−2,147,483,647, +2,147,483,647] 범위의 수를 표현할 수 있어야 하며, int형 보다는 그 표현범위가 크거나 같아야 하고, long long(int)형 보다는 그 표현범위가 작거나 같아야 한다. int에 2바이트가 할당되었을 때에는 이게 많이 쓰였다. 물론 지금은 고인.

6.4.5. long long (int) [편집]


(int128 계열을 제외하면) 제일 긴 정수 자료형이다. 비주얼 스튜디오에서는 비표준으로 64비트 정수형이라는 뜻에서 __int64로 쓰기도 한다.

대개는 8바이트 정수로 범위는 signed의 경우 -9223372036854775808~9223372036854775807(약 9.2×1018, 922경), unsigned의 경우 0~18446744073709551615(약 1.8×1019, 1844경)이다. 아무리 컴공이어도 이걸 외우진 않는다.

꽤 많이 쓰이는 자료형이다.

6.4.6. __int128 [편집]

16바이트 정수형이다. signed의 경우 -2^127 ~ 2^127-1, 즉 -170,141,183,460,469,231,731,687,303,715,884,105,728 ~ 170,141,183,460,469,231,731,687,303,715,884,105,727(약 1.7×1038, 170간) 까지의 범위를 가진다. 64비트 시스템에서만 사용할 수 있으므로 주의를 요한다.

6.5. 문자 자료형 [편집]

여러 가지 자료형이 있다. 가장 기본이 되는 것은 char이며, 이외 자료형의 사용법을 아래 표에 간략히 정리하였다.
자료형
리터럴 문법
필요 헤더
인코딩
컴파일러 의존성
printf/scanf 호환
비고
char
"..."
없음
ASCII
MBCS
비의존
O
기본 자료형
u8"..."
uchar.h
UTF-8
비의존
wchar_t
L"..."
wchar.h
와이드 문자(UCS-2/4)
의존
TCHAR
_T("...")
tchar.h
MBCS
와이드 문자(UCS-2)
비의존[10]
비표준 (Windows SDK)
char16_t
u"..."
uchar.h
UTF-16
비의존
X
char32_t
U"..."
uchar.h
UTF-32
비의존
X
int
'...'
없음
ASCII
의존
ISO/IEC 9899:TC3 §6.4.4.4

6.5.1. char [편집]

character를 줄인 단어이다. 어떤 사람은 '캐릭터'라고 읽기도 하고, 어떤 사람은 쓰여 있는 대로 '차'라고 읽는다.
6.5.1.1. 초기화 방법 [편집]
변수에 데이터를 저장하는 방식이 조금 다르다.

1. 문자를 사용
#include <stdio.h>

int main()
{
	char a;
	a = 'a';
	printf("%c", a);
	return 0;
}

6줄과 같이 작은따옴표 사이에 한 글자짜리 문자('a')를 넣어서 변수에 저장하는 것이다. 다른 말로 하자면, 'a'는 (ASCII CODE 기준) 숫자 97을 다른 식으로 표현한 것이고, 따라서 변수 a에는 숫자 97이 저장된다.

2. 문자 코드를 사용
#include <stdio.h>

int main()
{
	char a;
	a = 97;
	printf("%c", a);
	return 0;
}

컴퓨터에 저장되는 것은 사실 모든 것이 다 이진수이므로, 문자마다 숫자를 매겨서 그 숫자를 저장한다. 그러므로 1번이든 2번이든 a라는 문자가 아니라 a에 해당하는 문자 코드인 97이 저장된다.

3. 멀티시퀀스 리터럴
#include <stdio.h>

int main()
{
	int a;
	a = 'HELP';
	printf("%x", a); // 각 문자의 시퀀스인 48454c50 가 출력된다
	return 0;
}

주로 macOS[13]Clang마이크로소프트Visual C++에서 사용되는 비표준 형식.
자료형은 int 를 사용하게 되고 2바이트 이상의 문자 리터럴을 한 자료형에 넣는다.
C 표준 문서인 ISO/IEC 9899:TC3는 캐릭터 리터럴은 int로 대치될 수 있지만 ('a'0x61과 같이) 1개를 초과하는 정의하는 방법은 implementation-defined로 Undefined behavoiuor다. [14]

MSVC, Clang에서는 이 문법이 경고나 오류를 내지 않으나 GCC의 경우 경고를 출력한다.

{
	// Undefined behaviour, GCC에서 경고
	a = 'HELP';

	// C Conformance에 Compliant 하나 Endianness depend 하므로 x86을 비롯한
	// Little Endian 환경에서 48454c50 (PLEH)가 출력된다.
	a = *(int *)"HELP";

	// 가장 흔하게 사용되는 방법이자 권장되는 방법
	a = ('H' << 24 | 'E' << 16 | 'L' << 8 | 'P' << 0);
}
6.5.1.2. 인코딩 방식 [편집]
ASCII 방식과 UTF-8 방식 (C11 표준)을 보통 널리 사용한다. 그러나 EBCDIC 같은 표기법도 사용하는 경우가 있어 주의를 요한다.[15]
6.5.1.2.1. ASCII 인코딩 [편집]
C언어의 기존 방식에 따르면 char 자료형은 ASCII 인코딩을 사용하며 한 글자당 1바이트이다. 최소한 영어와 숫자를 포함한 128자(0~127)까지는 저장이 가능해야 한다.

2번처럼 코드로 문자를 저장하려면 a가 97이라는 걸 알아야 하는데, 대체 문자마다 어떤 숫자를 매겨놓는지는 어떻게 정해진 것일까? 이에 대한 자세한 설명은 인코딩이나 유니코드 등을 참조하되, 단 char의 크기인 1바이트 안에서 유니코드아스키 코드와 똑같으므로 간단한 내용을 보려면 아스키만 봐도 충분하다.

원칙적으로는 다국어를 지원할 수 없으나 최근의 컴파일러들은 MBCS 방식을 통해 char 자료형을 다국어로 인코딩하기도 한다. 다만, UTF-8 인코딩에 비해서 호환성이 심각하게 떨어질 수 밖에 없다.
6.5.1.2.2. UTF-8 인코딩 [편집]
C11 표준부터 char 자료형에 UTF-8 유니코드 인코딩을 사용할 수 있다. 원래는 C++11 표준에서 먼저 논의되었고 나중에 C11 표준에서도 도입되었다.

UTF-8 인코딩의 경우에는 ASCII 인코딩과 하위호환이 가능하므로 char 자료형에 저장되는 영어 문자의 숫자값들은 ASCII 인코딩으로 저장하는 경우와 동일하다. UTF-8로 인코딩하는 경우 한글의 경우에는 1자에 3바이트로 찍히는 경우가 많다.

u8"..." 리터럴을 통해 char 자료형을 사용하면 된다. uchar.h 헤더를 include해야 한다.

char 자료형에서 UTF-8 인코딩의 경우에는 GCC/G++ (버전 8.0.0)에서 정상적으로 동작한다.

비주얼 스튜디오에서는 로케일 및 코드 페이지 문제로 인해서 제대로 동작하지 않을 수 있다.
6.5.1.3. 예제 [편집]
C언어에서 char 자료형은 각종 자격시험 및 전공과목의 시험에서 자주 출제되는 소재이다.

1. char 자료형에서 문자와 숫자 간의 매핑 관계 파악하기
#include <stdio.h>

int main()
{
	char a;
	scanf("%c", &a);
	printf("%d", a);
	return 0;
}

뭔가 복잡할 것 같지만 은근 짧다. 원리는 간단하다. 문자를 입력받으면 아스키 코드로 저장되는데(6줄) 그것을 정수 형태로 출력(7줄)한 것이다. UTF-8 인코딩의 경우에는 영문 및 숫자 부분까지는 아스키 코드와 매핑이 같고 이외의 문자의 경우에는 다르다.

2.
#include <stdio.h>

int main()
{
	char a;
	a = 97;
	printf("%c", a + 1);
	return 0;
}

a는 97, 즉 a이다. 그런데 a+1을 출력하게 된다. a+1은 98이므로 98에 해당하는 아스키 코드인 b가 출력된다. UTF-8 인코딩으로 바꿔봐도 결과로 나오는 int 값은 동일하다. UTF-8 인코딩은 ASCII 인코딩과 하위호환이 되기 때문이다.

3. UTF-8 인코딩 방식을 통해서 char 자료형으로 입출력하는 예제이다.
#include <stdio.h>
#include <uchar.h>

int main()
{
	char a[20];

	printf(u8"입력해주세요.\n");
	scanf(u8"%[^\n]", a);
	printf(u8"%s(이)라고 입력하셨습니다.\n", a);

	return 0;
}

실행 결과는 다음과 같다.
입력해주세요.
안녕하세요.
안녕하세요.(이)라고 입력하셨습니다.

4. char 자료형을 UTF-8 방식으로 인코딩할 경우 한글 1자의 바이트를 구하는 예제이다.
#include <stdio.h>
#include <uchar.h>

int main()
{
	char a[]=u8"가";

	printf(u8"%s\n", a);
	printf(u8"총 %ld 바이트\n", sizeof(a));

	return 0;
}


실행 결과는 다음과 같다.

총 4 바이트

char 자료형은 NULL 종료 문자로 끝나므로 이 예제에서는 a[]가 '가' (a[0])와 '\0' (a[1], NULL 종료 문자)로 이루어져있고, '\0'은 항상 1바이트이고 UTF-8로 인코딩 된 '가'는 3바이트라는 것을 알 수 있다.

6.5.2. wchar_t [편집]

과거 C언어의 char 자료형이 ASCII 인코딩만 지원함으로 인해서 다국어 입출력이 불가능함을 해결하기 위하여 등장한 문자 자료형이다. 와이드 케릭터 방식으로 인코딩되며 윈도우의 경우에는 UCS-2 인코딩을 통해 한 글자당 기본 2바이트+a, 유닉스 및 리눅스의 경우에는 UCS-4 인코딩을 통해 한글자 당 기본 4바이트로 인코딩된다.

C11 표준으로 u8 리터럴과 char16_t, char32_t 자료형이 나오기 전까지는 다국어 호환성이 뛰어나기 때문에 어느 정도 쓰였다. 그러나 컴파일러 의존적인 자료형이기 때문에 컴파일러에 따라서 인코딩 결과가 달라진다는 점으로 인해 많은 개발자들이 사용하기 꺼려하는 자료형이다. 또한, 로케일 설정을 필요로 하므로 출력하는 과정도 매우 까다롭다.

L"..." 리터럴을 사용한다. wchar.h 헤더를 include해야 한다.
#include <stdio.h>
#include <wchar.h>

int main()
{
	wchar_t a[20];
	wscanf(L"%s", a);
	wprintf(L"%s", a);
	return 0;
}

6.5.3. TCHAR [편집]

Windows SDK에서 지원하는 호환성을 최대한 유지하면서 코드의 형태를 유지하기 위한 비표준 매크로이다. 컴파일러 설정에 따라서 MBSC 인코딩이나 와이드 캐릭터 (UCS-2) 인코딩 방식으로 해당 문자열을 인코딩하며 Windows에서만 사용되는 특성상 플랫폼 의존적인 자료형이다.[16] Windows는 기본 로케일이 각 언어별로 설정되기 때문. 간단한 매크로 함수 선언을 통해서 Windows에서 유니코드(Windows에서 유니코드는 보통 UTF-16을 의미한다)를 지원하면서 호환성이 있는 코드를 작성할 수 있도록 한 개념이다.
UNICODE 매크로가 활성화 되면 매크로로 TCHAR은 wchar_t로 선언되며, 선언되지 않으면 char로 선언되게 된다. _T(x) x 매크로도 마찬가지로 선언되면 L로, 선언되지 않으면 제거된다.

_T("...") 리터럴을 사용하고 tchar.h 헤더를 include해야 한다.
#include <stdio.h>
#include <tchar.h>
#include <Wincon.h>

int _tmain(int argc, TCHAR **argv)
{
	TCHAR a[20];
        SetConsoleTitle(_T("tchar test"));
	_tscanf(_T("%s"), a);
	_tprintf(_T("%s"), a);
	return 0;
}

위와 같은 코드는 _MBCS 가 선언되어 있으면 프리프로세서에서 다음과 같이 변환된다.

#include <stdio.h>
#include <tchar.h>
#include <Wincon.h>

int main(int argc, char **argv)
{
	char a[20];
        SetConsoleTitleA("tchar test");
	scanf("%s", a);
	printf("%s", a);
	return 0;
}


그리고 _UNICODE가 선언되면 다음과 같이 변환된다.

#include <stdio.h>
#include <tchar.h>
#include <Wincon.h>

int wmain(int argc, wchar_t **argv)
{
	wchar_t a[20];
        SetConsoleTitleW(L"tchar test");
	wscanf(L"%s", a);
	wprintf(L"%s", a);
	return 0;
}

6.5.4. char16_t [편집]

UTF-16 방식으로 인코딩하는 문자 자료형이다. C11 표준으로 등장하였다. 한 글자당 기본 2바이트+a로 저장되고 컴파일러에 의존적이지 않다.

u"..." 리터럴을 사용하고 uchar.h 헤더를 include해야 한다. 다만, printf()scanf() 함수와의 호환이 되지 않고 변수 초기화 이후에는 C++std::cinstd::cout 객체를 사용하지 않고서는 입출력이 거의 불가능하다.
#include <stdio.h>
#include <uchar.h>

int main()
{
	char16_t a[]=u"나무위키 프로그래밍 프로젝트";
	return 0;
}

6.5.5. char32_t [편집]

UTF-32 방식으로 인코딩하는 문자 자료형이다. C11 표준으로 등장하였다. 한 글자당 4바이트로 저장되고 컴파일러에 의존적이지 않다.

U"..." 리터럴을 사용하고 uchar.h 헤더를 include해야 한다. 다만, printf()scanf() 함수와의 호환이 되지 않고 변수 초기화 이후에는 C++std::cinstd::cout 객체를 사용하지 않고서는 입출력이 거의 불가능하다.
#include <stdio.h>
#include <uchar.h>

int main()
{
	char32_t a[]=U"안녕, 나무위키.";
	return 0;
}

6.6. 실수 자료형 [편집]

부동소수점 방식을 이용해 실수를 표현한다.

6.6.1. float [편집]

주로 4바이트 단정밀도로 실수를 표현하지만, 컴파일러에 따라 배정밀도로 표현할 수도 있다. 이때 표현할 수 있는 가장 큰 수는 2128(약 3.4028234664×1038(340간)), 가장 작은 양수는 2-149(약 1.4012984643×10-45)이다.

6.6.2. double [편집]

주로 8바이트 배정밀도로 실수를 표현한다. 이때 표현할 수 있는 가장 큰 수는 21024(약 1.7976931348623157×10308), 가장 작은 양수는 2-1074(약 4.9406564584124654×10-324), "안전하게" 표현할 수 있는 가장 큰 정수[17]는 253(9007199254740991(약 9×1015, 9007조))이다.

6.6.3. long double [편집]

double보다 더 높은 정밀도로 실수를 표현한다. 컴파일러에 따라 정밀도가 다르다.

6.7. void [편집]

말 그대로 아무 것도 없는 자료형이다. 실제로 이러한 자료형이 있는 것은 아니며, 뭔가 다른 의미를 나타낼 때 사용한다. void가 쓰이는 경우는 보통 3종류가 있다.
  • 함수를 정의할 때 파라미터에 사용하여 어떠한 인자도 받지 않음을 명시한다. (int foo(void) {...}) 이 void를 생략하면 '(가변인자 함수처럼) 지정되지 않은 타입의 인자를 지정되지 않은 개수만큼 받을 수 있다' 의 의미가 되어 인자를 받아도 컴파일 에러가 발생하지 않는 함수가 정의된다. C++은 생략하여도 '파라미터가 없음'을 의미한다.
  • 리턴값이 없는 함수의 자료형으로 쓴다. (void bar(int arg) {...}) 이 경우는 return문을 생략해도 된다.
  • 자료형을 모르는 포인터에 임시 자료형으로 부여한다(void*). 예를 들어 stdlib.h에 선언된 qsort 함수는 두 값을 비교하는 함수를 인자로 받는데, 이 함수가 인자로 void*형 2개를 받는다.

6.8. 배열 [편집]

배열은 변수들의 모음이다. 예를 들면 "Hello"라는 값은 H, e, l, l, o의 5개의 변수의 값의 모임이다[18]. 배열은 다음과 같이 선언한다.
변수의 이름[변수의 개수] = {넣을 값, 넣을 값, 넣을 값....};

이렇게 생겼다고 보면 된다.
arr[0]
arr[1]
arr[2]
(arr은 배열의 이름)

여기서 중요한 것은, 배열 번호(첨자라 한다)는 0부터 시작한다.

이 배열을 2차원으로도 만들 수 있다.
변수의 이름[변수의 개수][변수의 개수] = {{넣을 값, 넣을 값, 넣을 값....}, {넣을 값, 넣을 값, 넣을 값....}};

이건 이렇게 생겼다.
arr[0][0]
arr[0][1]
arr[0][2]
arr[1][0]
arr[1][1]
arr[1][2]
arr[2][0]
arr[2][1]
arr[2][2]

이런 식으로 2차원, 3차원, 4차원... 의 배열을 통틀어 다차원 배열이라 한다.

사용 예시:
#include <stdio.h>

int main()
{
	int arr[5] = {1,2,3,4,5};
        printf("%d", arr[2]);
}

출력 결과는 3이다. 첨자는 0부터를 반드시 기억하자.

6.9. 포인터 [편집]

포인터는 값 자체가 아니라 값이 저장된 주소를 담는 변수이다. int*과 같이 다른 자료형에 *를 붙여서 표시한다.

포인터 변수는 다음과 같이 선언한다. 띄어쓰기는 자유롭게 할 수 있다.
자료형* 변수이름; 혹은 자료형 *변수이름;

또한, 포인터 변수에는 다음과 같이 주소를 저장한다.
변수의 이름 = &다른 변수의 이름;
변수 이름 앞에 &를 넣으면 그 변수의 주소값을 의미한다. scanf 함수를 쓸 때 붙이는 &도 이것이다.

마지막으로 포인터 변수가 가리키는 곳의 값을 편집할 때는 다음과 같이 쓴다.
*변수의 이름 = 넣을 값;

6.9.1. 예시 [편집]

#include <stdio.h>

int main(void)
{
	int foo = 3, *ptr;
	ptr = &foo;
	*ptr = 5;
        printf("%d", foo);
}

위 프로그램의 출력값은 5이다.
  • 6줄에서 ptr이 변수 foo를 가리키게 설정하였다.
  • 7줄에서 ptr이 가리키는 곳, 즉 foo의 값을 5로 바꾼다.
  • 8줄에서 foo의 값을 출력한다. foo 자체를 건드리지는 않았지만 7줄에서 포인터 ptr을 통해 간접적으로 조작했으므로 3이 아닌 5가 출력된다.

6.9.2. 함수 포인터 [편집]

포인터는 정수뿐만 아니라 함수를 가리킬 수도 있다.
#include <stdio.h>

void printNamuWiki(void) {
	printf("Welcome to NamuWiki!\n");
}

int main(void)
{
	void (*funcPtr)(void) = &printNamuWiki; // &는 옵션. 
	(*funcPtr)(); // Welcome to NamuWiki! (*는 옵션)
	return 0;
}
함수 포인터를 사용할 때 선언문을 빼면 * 또는 &을 붙여 줄 필요가 없다. funcPtr()나 (*funcPtr)()나 (****funcPtr)()나 다 똑같다. &printNamuWiki가 아니라 &&printNamuWiki나 그냥 printNamuWiki를 사용해도 된다.

6.9.3. 복잡한 선언문 읽기 [편집]

int (**ptr[10])(void);
같은건 어떻게 읽어야 할까? 다음과 같이 아주 간단한 방법이 널리 알려져 있다.

Reading C type declarations

변수이름부터 시작해서, 연산자 우선순위에 따라 연산자들을 결합해 나가며 영어로 읽으면 타입을 알 수 있다. 혹은 반대로, 가장 바깥쪽부터 시작해서 연산자 우선순위가 낮은 순서대로 한국어로 읽으면 된다.

위의 예를 해석하자면, 변수 이름인 ptr부터 시작하여 그 옆에서 가장 우선순위가 높은 배열 참조 연산자를 읽는다.
ptr[10] -> array of 10...
**ptr[10] -> array of 10 pointer to pointer to
int (**ptr[10])(void); -> array of 10 pointer to pointer to function returning type int
즉 ptr의 정체는 배열이다.

한국어로 하자면 그 반대로 하면 된다. 바깥쪽부터 연산자 우선순위의 역으로 해석해 나가거나
int ( ... ) (void); -> int를 리턴하는 함수의
int ( * * ... ) (void); -> int를 리턴하는 함수의 포인터의 포인터의
int ( * * ptr[10] ) (void); -> int를 리턴하는 함수의 포인터의 포인터의 크기 10인 배열
혹은 영어와 마찬가지 방식으로 해석하되 한국어 어순에 맞게 문장을 우에서 좌로 써나가면 된다.
ptr[10] -> 크기 10인 배열
**ptr[10] -> 포인터의 포인터의 크기10인 배열
int (**ptr[10])(void); -> int를 리턴하는 함수의 포인터의 포인터의 크기10인 배열
또 다른 예로
int **ptr(int, int);
는 포인터를 반환하는 함수다. 더 자세하게 말하자면
"a function that takes an int and an int and returns a pointer that points to a pointer that points to an int"이다.
(정수를 가리키는 포인터를 가르키는 포인터를 반환하는, 패러미터가 정수와 정수인 함수)

6.9.4. 동적 할당 [편집]

stdlib.h를 include하면 malloc이라는 함수가 따라오는데, 이 함수를 이용해서 원하는 만큼의 공간을 가져와서 쓸 수 있다. 이 공간의 시작 지점을 가리키는 포인터를 통해서만 접근할 수 있으므로, 당연히 포인터가 필요하다. 문법은 다음과 같다.
ptr = (자료형*)malloc(할당할 크기);

보통 다음과 같이 원하는 길이의 배열처럼 쓰는 경우가 많다.
ptr = (int*)malloc(배열의 길이 * sizeof(int));
이렇게 할당하고 나면 ptr[5]와 같이 사용할 수 있다.

이렇게 동적 할당을 할 때 주의할 점은, 한 번 할당된 공간은 프로그램이 종료되기 전까지 해제되지 않기 때문에 할당받은 공간을 다 사용하면 프로그래머가 직접 해제해야 하며, 해제하지 않으면 필요 이상의 메모리를 사용하게 되어 심할 경우 프로그램이 그대로 뻗는 경우도 있다. [19]메모리 해제는 free(ptr);과 같이 free 함수로 할 수 있다.

사실 동적 할당에서 형변환이 필요하지는 않다. 아래처럼 쓸 수도 있다.
ptr = malloc(할당할 크기);
ptr = malloc(배열의 길이 * sizeof(int));

malloc 함수는 어떤 자료형을 담는 공간이라도 만들 수 있는 함수이므로, 그 공간의 시작 포인터가 당연히 int *형이나 double *형으로 한정되어서는 안 될 것임은 자명하다. 그러나 예전에는 void *라는 모든 포인터를 포괄하는 포인터가 없었고 대신 1Byte씩 접근을 할 수 있는 char *형으로 반환했었다. char *형도 범용으로 쓸 수는 있지만 어쨌거나 본래 용도는 char형의 포인터이므로 당연히 형변환이 필요했었다. 이후 범용 포인터형인 void *가 생겨나면서 굳이 형변환을 하지 않아도 되게 바뀌었다. 그러나 예전부터 써온 관습도 있고 C++은 C와 void *형에 대한 규약이 달라 동적 할당에서 형변환이 필요하기 때문에, 대부분의 C 교재나 강의에서는 malloc 함수의 값을 형변환해서 쓰도록 가르치고 있다.

6.10. 형변환 [편집]

7. 함수 [편집]

함수는 C언어에서 처리하는 명령이다. 기본적으로 이렇게 생겼다,
함수 이름(입력 인자);

함수 원형부를 포함한 함수 바디라인은 이렇게 정의한다.
[출력 자료형] [함수 이름](입력 인자)
{
	[함수 내용]
}


예를 들어, 두 수를 입력 받아 그 수의 합을 돌려 주는 함수는 이렇게 생겼다.
int sum(int a, int b)
{
	int result;
	result = a+b;
	return result;
}

위 코드에서 알 수 있듯이, return은 그 뒤에 있는 값을 결과물로 내보내고, 함수를 끝낸다.
한편, 함수에서 return한 값을 받는 방법은 다음과 같다.
int num;
num = sum(3,5);

이 경우, num에는 8이 저장된다.

GCC C99 기준 자료형을 명시하지 않을 경우 int로 처리되며 return 문이 없으면 쓰레기 값을 반환한다.

7.1. main 함수 [편집]

main 함수는 안에 있는 구문들을 실행시키는 함수이다. 1개의 프로그램당 하나만 사용 가능하며, 일반적으로는 이렇게 쓴다.
int main()
{
	[실행할 코드]
	return 0;
}


main 함수에 입력 인자를 받는 경우에는 다음과 같이 정의해야 한다.
int main(int argc, char* argv[], char* envp[])
{
	[함수 내용]
	return 0;
}


argc는 컴파일된 프로그램이 받는 매개변수의 개수이고, argv[]는 매개변수 문자열들의 배열이다. argv[]는 argv[argc]를 의미한다. 배열 argv[]의 첫번째 원소 argv[0]은 프로그램 자신의 이름이다. 입력받는 매개변수의 문자열들은 자동적으로 원소 argv[1]부터 원소 argv[argc-1]까지 입력받은 순서대로 저장된다.

envp[]는 운영체제 환경변수 문자열들의 배열이다. argv[]는 자주 쓰이나 envp[]는 거의 쓰이지 않고 main 함수의 매개인자에서 생략되는 경우도 많다. envp가 없어도 환경변수 문자열에 접근하는 방법이 있으므로 없어도 된다.

저차원적인 프로그램에서는 매개인자 없이 int main(void) 또는 int main()으로 main 함수를 정의하는 경우가 많지만 상업용 프로그램 및 대규모 프로그램에서는 주로 int main(int argc, char* argv[])로 main 함수를 정의한다.

리턴값을 void로 정의할 수도 있지만 이는 비표준적인 문법이다. main 함수에서는 원칙적으로 프로그램 종료 코드를 숫자로써 int 자료형으로 리턴하는 것이 표준이기 때문이다. 암시적으로 main 함수에서 0을 리턴하는 경우에는 정상 종료를 의미하고 이외의 숫자가 리턴되면 비정상 종료를 의미한다. 다만, 시그널 함수를 이용해서 종료 코드를 사용하지 않더라도 프로그램 자신의 종료 상태를 운영체제에 전달할 수 있다.

C언어로 짜여진 유닉스 및 리눅스 프로그램의 소스 코드에서는 리턴값 0이 정상 종료를, -1이 비정상 종료를 의미하는 경우가 많다. 특히, 시스템 호출 함수에서 그렇다.
main 함수를 void로 리턴값을 정의하거나, int로 리턴값을 정의했으나 return 0; 구문이 없는 경우에는 운영체제가 암시적으로 종료 코드를 0으로 인식한다.

7.2. 내장 연산자 [편집]

C언어에서 아무런 헤더파일도 선언하지 않고 쓸 수 있다.

7.2.1. sizeof [편집]

sizeof(...)와 같이 쓴다. 인자로는 자료형, (특정한 자료형을 갖는) 변수나 수식 등이 온다.
반환값은 정수형으로, 그 변수나 수식에 할당된 저장 공간을 바이트 단위로 표시한다. 배열의 경우 (배열의 길이)×(배열에 저장되는 자료형 자체의 데이터 할당량)이 된다.

엄밀히 말하면 sizeof는 함수가 아니라 단항 연산자이다.

GCC 기준으로는 괄호는 생략할 수 있다.
7.2.1.1. 예시 [편집]
#include <stdio.h>

int a[100];

int main()
{
	printf("%d\n", sizeof(a));
	return 0;
}

배열의 길이는 100이고 int의 공간 할당량은 4바이트이므로 출력결과는 4×100인 400이다.

7.3. 헤더 파일 [편집]

헤더 파일은 이미 있는 함수들의 모임이다. 프로그램의 가장 앞에 선언하면 그 안의 함수를 쓸 수 있다.
문법은 다음과 같다.
#include <헤더 파일 이름>

7.4. 함수 일람 [편집]

7.4.1. 입/출력 함수 [편집]

7.4.1.1. printf [편집]
int printf(const char *format, ...)

printf는 문자열(문장) 이나 변수를 화면에 출력하는 함수이다. 여기서 f는 format으로, 형식을 줘서 출력한다는 뜻이다. printf 함수는 리터럴과 출력에 들어갈 변수의 두 가지를 입력 받는다.
7.4.1.1.1. 기본 문법 [편집]
printf의 기본 문법은 다음과 같다.
printf(리터럴[문자열 + 서식 지정자], 변수1, 변수2, ...);
이때 서식 지정자(format specifier)가 있는 자리에 변수가 차례로 들어간다.

예를 들어, a가 4라면,
printf("a의 값은 %d 입니다.", a);
a의 값은 4 입니다.
이런 식이다. 이때 %d가 서식 지정자이다. 옵션과 변수의 위치가 바뀌는 셈.
7.4.1.1.2. 리터럴 [편집]
ASCII 인코딩의 char 자료형은 리터럴이 필요없으나, char 자료형을 UTF-8 인코딩으로 출력하는 경우에는 u8 리터럴을 사용해야 한다.

wchar_t 자료형에서는 L"..." 리터럴을, tchar 자료형에서는 _T(...) 리터럴을 사용한다.
7.4.1.1.3. 서식 지정자 [편집]
자세한 서식 지정자 문법은 영문 위키백과를 참조하면 된다.

대소문자가 따로 있는 서식 지정자의 경우 출력 결과의 대문자/소문자 여부가 바뀐다.
  • %%: '%' 문자. 문자열에 % 하나만 써 놓으면 서식 지정자로 취급되어 출력되지 않기 때문에 이렇게 써야 한다.
  • 정수
    • %d: 부호 있는 정수를 10진수(decimal)로 출력. 아래와 같이 별도 설정을 하지 않으면 int형의 범위 내에서 출력된다.
      • %hhd: char형
      • %hd: short형
      • %ld: long int형
      • %lld: long long int형
    • %u: 부호 없는(unsigned) 정수를 10진수로 출력. %d처럼 자료형의 길이를 지정할 수 있다.
    • %x/%X: 부호 없는 정수를 16진수(hexadecimal)로 출력.
    • %o: 부호 없는 정수를 8진수(octal)로 출력.
  • 소수
    • %f/%F: xxx.xxxx... 형태로 출력. 아래와 같이 별도 설정을 하지 않으면 double형으로 취급된다. 무한대나 NaN이 저장되어 있을 경우 %f%F 중 무엇을 쓰느냐에 따라 대소문자가 바뀐다.
      • %lf: %f와 같다.
      • %Lf: long double형
      • %g: 뒤의 의미없는 0을 생략한다.
    • %e/%E: 지수 표기법(x.xxxE[+/-]xx)으로 출력. 예를 들어 150은 1.5e02로 출력된다.
    • %a/%A: 16진수로 출력. 0x로 시작한다.
  • 문자열
    • %c: 문자 하나(char)를 출력
    • %s: 문자열(char[])을 출력

위 서식 지정자에 추가로 옵션을 붙일 수 있다. %f를 예로 들면 다음과 같다.
  • %+f: 양수일 경우 왼쪽에 +를 붙인다.
  • % f: 양수일 경우 왼쪽에 띄어쓰기를 1칸 붙인다. +가 있을 경우 무시한다.
  • %(숫자)f: 최소한 (숫자)만큼의 길이로 출력한다. 길이가 짧을 경우 왼쪽에 부족한 만큼 띄어쓰기를 한다. 예를 들어 5를 %3d로 출력하면 __5(띄어쓰기 대신 언더바를 붙였음)가 된다.
    숫자가 올 자리에 *를 쓰면 인자로 받은 변수만큼의 길이로 출력한다. 예를 들어 printf("%*d", 4, 12);의 출력값은 __12이다.
  • %0(숫자)f: 위와 같지만, 왼쪽에 부족한 만큼 0을 붙인다. 예를 들어 5를 %03d로 출력하면 005가 된다. 위와 같이 *를 쓸 수 있다.
  • %.(숫자)f: 소수점 아래 (숫자)자리에서 반올림해서 출력한다. 문자열에 사용할 경우 (숫자)글자만큼만 출력된다. 예를 들어 0.1234를 %.2f로 출력하면 0.12가 된다. 위와 같이 *를 쓸 수 있다.

서식 지정자의 동작은 다소 기묘한 면이 많다. 예를 들어 printf 함수에서는 char형 변수와 int형 변수를 모두 %d 서식자로 출력 가능하나, scanf 함수에서는 char형 변수를 문자로 입력받을 때는 %c, 숫자로 입력받을 때는 %hhi, int형 변수를 입력받을 때는 %d를 사용한다. 이러한 차이점들은 가변인자 함수의 동작 방식(좀 더 정확히는 가변인자함수의 매개변수에 적용되는 기본인자진급 규칙)을 깊이있게 공부하면 이해가 가능해진다.
7.4.1.2. scanf [편집]
int scanf(const char *format, ...)

scanf는 값을 입력받는 함수이다. scanf 함수는 옵션과 입력값을 저장할 변수의 포인터, 즉 그 변수의 주소 두 가지를 입력 받는다. 쉽게 설명하기 위해 택배에 비유하자면 입력값은 택배 내용물, 변수의 주소 값은 택배에 쓸 주소이다.
7.4.1.2.1. 서식 지정자 [편집]

printf와 같은 서식 지정자를 쓰지만, *의 의미가 '해당하는 값 하나를 읽은 다음 저장하지 않고 버리는 것'으로 바뀌는 등 소소한 변화가 있다. 예를 들어 scanf("%*d %d", &a);에서 3 5를 입력받았을 경우 3을 버리고 a에 5를 저장한다.

printf함수의 서식 지정자와 비교해보면, scanf는 입력받는 매개변수의 타입에 좀 더 신경을 써서 서식 지정자를 지정해 주어야 한다. printf함수에서는 가변함수의 기본인자진급 규칙으로 인해 int형과 double형 위주로 '대충'[20] 값을 넘겨받는데 비해 scanf함수는 포인터 주소값을 매개변수로 넘겨받은 뒤 표준입출력으로 입력받은 값을 포인터 주소값 위치에 직접 써 주기 때문에 꼼꼼히 신경써서 지정해 주지 않으면 잘못된 메모리 공간에 대해 쓰기를 시도할 수 있다.

서식 지정자 이외의 다른 문자열도 넣을 수 있는데, 이렇게 하면 다른 문자열을 읽은 이후에 서식 지정자에 해당하는 값을 읽어온다. 예를 들어 scanf("abc%d", &a)처럼 코딩하고 입력값으로 abc10을 넣으면 abc를 읽어온 후에 버리고 10을 읽는다. abcd, a1000 같은 문자열을 입력한 결과는 정의되지 않았기 때문에 주의해야 한다. scanf("%d, %d", &a, &b)로 코딩하고 3 5를 입력하면 a에는 3이 정확히 들어가지만 b에는 값이 들어가지 않는다. 3, 5로 입력해주어야 한다.

8. 반복문 [편집]

8.1. for [편집]

for(exp1; exp2; exp3){(statement)}
우선 exp1을 수행한다. 그 이후 exp2가 참인 동안 statement를 수행하고 exp3를 수행한다.
즉, 아래 구문과 완전히 동일하다. for(;;)이라고 쓰면 무한루프를 돈다.

exp1;
while(exp2)
{
    statement;
    exp3;
}

8.2. while [편집]

while(expression) {(statement)}
expression 조건식이 참인 동안 statement를 반복 실행하는 코드이다. while문에 진입할 때 expression이 거짓이라면 statement는 실행되지 않는다.

8.3. do-while [편집]

do{(statement)}while(expression)
while과 비슷하지만 do 안의 statement를 1회 실행 후 while 안의 조건이 참이라면 재반복, 거짓이라면 통과하는 코드이다.

9. 무조건 분기문 [편집]

특정 위치로 분기하는 명령어들.

9.1. goto [편집]

label:
...

goto label;

label 위치로 무조건 점프한다.

9.2. break [편집]

가장 안쪽의 루프, 혹은 switch 문을 빠져나간다.

9.3. continue [편집]

가장 안쪽 루프의 시작 위치로 되돌아간다.

9.4. return [편집]

함수를 종료하고 호출자에게로 되돌아간다.

10. 조건 분기문 [편집]

10.1. if [else] [편집]

if (expression) statement1 [else statement2]
() 속의 조건식(expression)이 참이 되면 statement1을, 거짓이면 statement2를 실행하는 구조로 되어 있다. else 이하는 생략 가능하며 else 뒤에 if를 다시 사용하여 if ... else if ... else if ... else 와 같이 사용할 수도 있다.

아래는 if문을 사용한 홀짝 판별법.
#include <stdio.h>

int main()
{
 int a; // 수를 입력할 버퍼를 정의한다.
 printf("수를 하나 입력하시오\n");
 scanfs("%d", &a); // 버퍼에 수를 입력받는다.
  if(a%2==1) // 버퍼 a를 2로 나눴을 때 나머지가 1이면 T, 0이면 F가 반환된다.
   printf("입력한 수 %d는 홀수입니다.", a); // 조건식에서 T가 반환되었을 때 실행되는 부분.
  else
   printf("입력한 수 %d는 짝수입니다.", a); // 조건식에서 F가 반환되었을 때 실행되는 부분.
}


다음은 else if의 예제로 두 정수와 연산자를 하나 입력했을 때, 두 수를 후에 입력한 연산자대로 계산하는 코드.
#include <stdio.h>

int main()
{
 int a, b; // 정수값을 입력할 변수를 정의한다.
 char op; // 연산자를 입력받을 변수를 정의한다.
 printf("정수값을 둘 입력하시오\n");
 scanfs("%d %d", &a, &b); // 변수에 수를 입력받는다.
 fflush(stdin); // C언어의 특성상 숫자를 문자보다 먼저 입력받을 경우 버퍼를 클리어해야 한다.
 printf("연산자를 입력하시오. 입력할 연산자는 +, -, *, /의 4가지.\n");
 scanfs("%c", &op); // 변수에 문자값을 입력받는다.
  if(c=='/') // div0을 막기 위해서 먼저 나눗셈을 정의한다.
    {if(b!=0) //제수가 0이 아닐 경우
     printf("%d / %d=%.3f", a, b, (float)a/b); // %.3f : 소수점 아래 3자리까지만 표시.
    else //제수가 0일 경우
     {printf("0으로 나눌 수 없습니다");};};//0으로 나눌 수 없다고 경고
  else if(c=='+') // 나눗셈이 아니면 덧셈을 정의.
    {printf("%d+%d=%d", a,b, a+b);}
  else if(c=='-') // 덧셈이 아니면 뺄셈을 정의.
    {printf("%d-%d=%d", a,b,a-b);}
  else if(c=='*') // 뺄셈도 아니면 곱셈을 정의.
    {printf("%d*%d=%d", a,b, a*b);}
}


10.2. switch ... case ... [편집]

switch(exp)
{
    case A:
        statementA;
    case B:
        statementB;
        break;
    default:
        statementDefault;
        break;
};

exp문의 값에 따라 해당하는 case로 분기하여 다음 문장을 break를 만나기 이전까지 수행한다. exp문과 case 다음의 값은 정수형((unsigned) char, int, long, long long)만 가능하다. 만일 일치하는 case 값이 없다면 default로 분기한다. 위의 예제에서 exp값이 A라면 statementA, statement B를 수행하고, B라면 statementB만을 수행한다. 둘 다 아니라면 statementDefault를 수행한다. 즉, break문을 만나면 switch문을 빠져나온다.

11. 관련 문서 [편집]

[1] 예약어/Reserved Word라고도 한다.[참고] a ^ (b ^ a) == (a ^ b) ^ a == b[참고2] a ^ b == b ^ a[4] 64-Bit Programming Models [5] 64-Bit Programming Models Standard data types on UNX, LINUX, and Windows [6] 이것은 UCS-2나 UCS-4를 출력하거나 처리하는 것과는 전혀 다른 이야기이므로 오해하지 않기를 바란다. [7] 표현할 수 있는 자리수도 길고 글자수도 길다.....[8] C언어 표준을 철저하게 준수하는 GCC 컴파일러 기준.[9] wprintf/wscanf와 호환된다.[10] TCHAR 문단 참조. 컴파일러의 확장이 아닌 단순 매크로이다.[11] tprintf/tscanf와 호환된다.[12] 컴파일러에 따름[13] 주로 SDK[14] The value of an integer character constant containing more than one character (e.g., 'ab'), or containing a character or escape sequence that does not map to a single-byte execution character, is implementation-defined. - ISO/IEC 9899:TC3 Programming languages — C §6.4.4.4[15] EBCDIC에서는 영어 소문자 판별을 위해 c >= 'a' && c <= 'z' 와 같은 조건식을 사용할 수 없다. 영문자가 연속적으로 배치되어 있지 않기 때문이다. [16] 컴파일러의 확장이 아니다[17] 이 한계보다 큰 정수도 표현할 수는 있지만, 정확성을 잃는다. 가령 9876543219876543을 입력하면 9876543219876544가 된다.[18] C에서 문자열은 항상 null 문자로 끝나기 때문에 실제로는 \0까지 합쳐 6개이다.[19] 그러면 malloc 함수가 NULL을 반환하므로 확인하자[20] 좀 더 정확히는, char형이나 short형은 int형으로, float형은 double형으로 변환되어 넘어간다

라이선스를 별도로 명시하지 않은 문서는 CC BY-NC-SA 2.0 KR에 따라 이용할 수 있습니다.
기여하신 문서의 저작권은 각 기여자에게 있으며, 각 기여자는 기여하신 부분의 저작권을 갖습니다.

문서의 기여자는 역사 탭에서 확인할 수 있습니다.
접두어의 N: - 나무위키 사용자, R: - 리그베다 위키의 사용자를 뜻합니다.
자세한 사항은 나무위키에서 동일한 문서의 역사를 참고하시기 바랍니다.