프로그래밍 언어 문법 | ||
{{{#!wiki style="margin: -16px -11px; word-break: keep-all" | <colbgcolor=#0095c7><colcolor=#fff,#000> 언어 문법 | C(포인터 · 구조체 · size_t) · C++(자료형 · 클래스 · 이름공간 · 상수 표현식 · 특성) · C# · Java · Python(함수 · 모듈) · Kotlin · MATLAB · SQL · PHP · JavaScript · Haskell(모나드) |
마크업 문법 | HTML · CSS | |
개념과 용어 | 함수(인라인 함수 · 고차 함수 · 람다식) · 리터럴 · 상속 · 예외 · 조건문 · 참조에 의한 호출 · eval | |
기타 | #! · == · === · deprecated · NaN · null · undefined · 배커스-나우르 표기법 | |
프로그래밍 언어 예제 · 목록 · 분류 | }}} |
1. 개요2. 기본3. 편집 지침4. 컴파일 과정 및 문법적 요소들
4.1. 어휘 요소(lexical elements)
5. 수식(expression)6. 변수, 자료형6.1. 기본 데이터 모델6.2. 변수 선언6.3. 변수 값 변경6.4. 정수 자료형
7. 함수8. 반복문9. 무조건 분기문10. 조건 분기문11. 기타 표준 라이브러리의 기능6.4.1. char, signed char, unsigned char6.4.2. short
6.5. 문자 자료형6.6. 실수 자료형6.7. void6.8. 배열6.9. 포인터6.10. 혼합형6.11. 형변환6.4.2.1. 예시
6.4.3. int6.4.3.1. 예시
6.4.4. long (int)6.4.5. long long (int)6.4.6. __int128_t11.1. stdbool.h
12. 관련 문서1. 개요
C언어의 문법을 설명하는 문서다.2. 기본
기본적으로 C언어의 코드의 구조는 다음과 같다.[헤더 파일 include]
[함수 선언]
[main 함수]
이들 중 헤더 파일 include는 쉽게 설명해서 C언어가 가지고 있는 함수를 불러오는 부분이다. 함수 선언은 자신이 만든 함수를 등록하는 부분, main 함수는 함수, 즉 명령을 그 안에서 처리한다.
3. 편집 지침
소스 코드로 예시를 들 때\#!syntax cpp (소스코드) |
예시(입력):
{{{#!syntax cpp
#include <stdio.h>
int main()
{
printf("hello world!\n");
return 0;
}
}}}
예시(출력):
#!syntax cpp
#include <stdio.h>
int main()
{
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)
문자열을 나타내기 위해 "Hello!"와 같이 큰 따옴표로 둘러싸인 것을 문자열 상수라 한다. 문자열 상수의 정체는 변경 불가능한 char형 배열이다.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(비트 AND) operator
- Bitwise exclusiive OR(비트 XOR) operator
- Bitwise inclusive OR(비트 OR) operator
- Logical AND(논리 AND) operator
- Logical OR(논리 OR) operator
- Conditional(삼항조건) operator
- Assignment(할당) operator
- Comma(쉼표, 콤마) operator
위로 갈수록 우선순위가 높다.
5.1. 산술 연산자
연산자 | 뜻 |
+ | 덧셈 또는 양의 단항 연산자 |
- | 뺄셈 또는 음의 단항 연산자 |
* | 곱셈 |
/ | 나눗셈 |
% | 나머지 |
5.2. 할당 연산자
변수에 자료를 할당할때 쓰이는 연산자들. 가장 기본적으로=
가 있고 편의를 위해서 다른 이항 연산자 옆에 =
를 붙인것들이 있다. 예를 들면 x *= 3
는 x = 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을 빼기 |
전위와 후위 증감 연산자의 차이점을 보여주는 코드:
#!syntax cpp
int a = 3; // a := 3
int b = 3; // b := 3
int x = ++a; // x := 4, a := 4
int y = b++; // y := 3, b := 4
이제
a
와 b
는 둘 다 4
다. x
는 4
지만 y
는 아직 3
이다.++a와 a++ 사이에 성능 차이가 있다는 소문이 있으나, 이는 C 컴파일러의 최적화가 미숙했던 오래전 이야기이고, 요새 기준으로는 별 의미없는 이야기이다. C++ 기준으로는 연산자 오버로딩이나 r-value값의 생성 등으로 의미있는 차이가 생길수 있으나 C와는 거리가 먼 얘기이다. 정 신경쓰인다면 전위연산자 ++a를 기본으로 쓰도록 하자.
5.4. 비트 연산자
말그대로 비트 연산자들로, AND OR XOR NOT 등이 있다. 이 연산자들 자체에 대해선 논리 연산으로.&
: AND|
: OR^
: XOR~
: NOT>>
: 비트를 오른쪽으로 이동<<
: 비트를 왼쪽으로 이동
=
를 붙이면 어떤 연산을하고 원래 값에 대입하라는 뜻. 예를 들어서 x >>= 2
는 x = x >> 2
와 같다.int
변수를 생성할때 값을 2진수로 쓰고 싶다면 앞에 0b
를 붙이면 된다. 예를 들면 int n = 5
는 int n = 0b101
과 같다.사용 예시 1: 값이 짝수인지 홀수인지 구별해서 출력해주는 함수
- [펼치기]
#!syntax cpp #include <stdio.h> void oddOrEven(int n){ if (n & 1){ printf("홀수\n"); } else { printf("짝수\n"); } }
사용 예시 2: 정수를 2진수 (32비트)로 바꿔서 출력하는 함수
- [펼치기]
#!syntax cpp #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]- [펼치기]
- 최솟값
#!syntax cpp int min(int a, int b){ return b ^ ((a ^ b) & -(a < b)); }
최댓값#!syntax cpp int max(int a, int b){ return a ^ ((a ^ b) & -(a < b)); }
비트를 열거형으로써 사용할 수도 있는데, 이것들을 비트 연산자들로 조작이 가능하다. 예를 들어서 어떤 프로그램을 만드는데 파랑, 초록, 노랑, 빨간 불빛이 나오는걸 컨트롤 하고싶다고 치자. 어떤 불이 켜져있는지의 상태를 플래깅 하기 위해서는 1개의 정수면 충분하다.
#!syntax cpp
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보다 크면/크거나 같으면 참,아니면 거짓이다. |
5.6. 3항 연산자
a? b:c
의 형태로 a값이 참(non-zero)이면 b, 거짓이면 c이다. a = (b == 10)? 20:30
이라고 쓴다면 b가 10일 때 a에는 20이, 그 이외에는 30이 대입된다. 다른 이항 연산자들과 유사하게, 2번째 항과 3번째 항 사이에 일반 산술 변환 과정을 거친다[4].5.7. sizeof
sizeof(...)
와 같이 쓴다. 인자로는 자료형, (특정한 자료형을 갖는) 변수나 수식 등이 온다.반환값은 정수형으로, 그 변수나 수식에 할당된 저장 공간을 바이트 단위로 표시한다. 배열의 경우 (배열의 길이)×(배열에 저장되는 자료형 자체의 데이터 할당량)이 된다.
GCC 기준으로는 인자가 자료형이 아닌 경우에 한해 괄호를 생략할 수 있다.
5.7.1. 예시
#!syntax cpp
#include <stdio.h>
int a[1000];
int main()
{
printf("%d\n", sizeof(a));
return 0;
}
배열의 길이는 100이고 int의 공간 할당량은 4바이트이므로 출력결과는 4×1000인 4000이다.
6. 변수, 자료형
C언어의 변수에는 자료형이라는 게 있다. 변수를 상자에 비유한다면 자료형이란 상자의 모양이 된다. 상자에 넣을 내용물에 따라 상자의 모양을 결정하듯이, C언어의 자료형은 변수에 넣을 내용물에 따라 결정된다.C언어에는 Standard signed integer types라 하여 char, short, int, long, long long의 5가지 기본 (부호 있는) 정수형이 존재한다. 이 다섯 가지는 서로 랭크가 다르며, 뒤로 갈수록 랭크가 높다. 랭크가 높으면 표현 범위가 더 크거나 같다.
정수형의 크기는 컴파일러(좀 더 정확히는 사용하는 환경 자체를 가리켜 구현체(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언어에는 Real floating type이라 하여 float, double, long double형의 부동소수형이 존재하고, 그 표현 범위는 뒤로 갈수록 같거나 커져야 한다. 언어 자체적인 약속은 딱 여기까지이고, 보통은 IEEE 754라는 부동소수점 표현 규격에 얹혀간다.
C언어에서 char, signed char, unsigned char형은 문자형(character type)이라 분류한다. char형은 signed char형과 unsigned char형 중에서 하나와 동일한 표현 범위, 동작을 갖는다.(그러나 그렇다고 해서 동일한 타입이 되는 건 아니다.)
정수형, 부동소수형, 문자형의 세 가지 타입을 통틀어 기본형(basic type)이라 부른다.
6.1. 기본 데이터 모델
C언어는 기본적으로 다양한 컴퓨터 환경을 염두에 두고 만들어졌기 때문에, 각각의 데이터 형들의 크기는 컴파일러마다 다를 수 있다. 일반적으로 널리 쓰이는 컴퓨터와 OS 위에서의 데이터 모델의 종류와 범위는 다음과 같다.64-Bit Programming Models
주요 컴퓨팅 환경에서 사용하는 메모리 모델은 다음과 같다.
- ILP32: 32비트 윈도를 포함한 대부분의 32비트 시스템
- LP32: 윈도 3.1의 win-16 API, 애플 매킨토시
- LLP64: 64비트 윈도
- LP64: 64비트 리눅스
6.2. 변수 선언
자료형 변수이름; |
예시:
#!syntax cpp
int a; // 정수형 변수 a를 선언한다.
a
라는 변수를 int라는 자료형으로 선언한다. 이때 갓 선언된 변수에는 보통 의미가 없는 쓰레기 값(garbage value)이 들어 있으며, 일부 IDE에서는 쓰레기 값이 들어있는 변수를 쓰려고 하면 오류를 내기도 한다.6.3. 변수 값 변경
변수에 값을 대입하는 법은 다음과 같다.변수의 이름 = 넣을 값; |
#!syntax cpp
#include <stdio.h>
int main()
{
int a;
a = 10;
return 0;
}
다음과 같이 초기화와 대입을 한 번에 할 수도 있다.
자료형 변수의 이름 = 넣을 값; |
#!syntax cpp
#include <stdio.h>
int main()
{
int a = 10;
return 0;
}
6.4. 정수 자료형
정수만 들어갈 수 있다. 만약 실수가 들어갈 시 소수점 뒤 부분은 사라지고 앞부분만 들어간다.#!syntax cpp
#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이다. 하지만 이것이 char형이 모든 하드웨어에서 똑같은 크기인 것을 의미하지는 않는다. C언어에서는 1byte란 8bits 이상의 크기를 가지는, 주소지정 가능한 메모리의 최소 단위일 뿐이기 때문이다.[5]
6.4.2. short
가장 짧은 정수 자료형. 쓰는 일은 거의 없다. 컴퓨터는 몇 비트씩 묶은 WORD를 기준으로 자료처리를 하는데, short는 기준이 되는 WORD보다도 짧아서 처리 속도가 느려지기 때문이다. 메모리든 하드든 저장 공간 절약이 중요하던 시절에는 short도 많이 썼었다.최소 2바이트 정수이고 이때 signed는 최소한 -32767~32767, unsigned는 최소한 0~65535까지 저장이 가능하다.
6.4.2.1. 예시
#!syntax cpp
#include <stdio.h>
int main()
{
short a;
a = 32768;
printf("%d", a);
return 0;
}
이 프로그램의 출력결과는 32768이 아니다. 그 이유는
a
라는 변수는 자료형이 short이기 때문에 32767까지 저장이 되어서 값이 제대로 저장이 되지 않기 때문이다.#!syntax cpp
#include <stdio.h>
int main()
{
short a;
a = 33;
printf("%d", sizeof(a));
return 0;
}
이 프로그램의 출력결과는 short 자료형의 바이트 수인 2이다.
6.4.3. int
선언되는 빈도가 제일 높다. 보통 int를 시스템의 WORD와 같게 맞추기 때문에 속도도 잘 나온다. short는 너무 사이즈가 작고 long long int는 ~위에서 말한 대로 그 최소 표현 범위는 −32,767~32,767(-(2^15 - 1) ~ (2^15 - 1))이며, 컴파일러와 OS의 정수형 메모리 모델에 따라 굉장히 다양한 표현범위가 가능하므로, 자신이 사용하는 환경에서의 범위를 미리 확인해 두는 것이 좋다. 이식성을 고려한다면, 이러한 것들을 염두에 두고 코드를 작성해야 한다.
6.4.3.1. 예시
#!syntax cpp
#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(=2^31-1)] 범위의 수를 표현할 수 있어야 하며, int형 보다는 그 표현범위가 크거나 같아야 하고, long long(int)형 보다는 그 표현범위가 작거나 같아야 한다. int에 2바이트가 할당되었을 때에는 이게 많이 쓰였다. 물론 지금은 고인.6.4.5. long long (int)
(int128 계열을 제외하면) 제일 긴 정수 자료형이다. 비주얼 스튜디오에서는 비표준으로 64비트 정수형이라는 뜻에서 __int64로 쓰기도 한다.대개는 8바이트 정수로, 그 때의 범위는 signed의 경우 (263 - 1) ~ (263) 범위이고 이를 10진수로 표현하면 -9 223 372 036 854 775 808~9 223 372 036 854 775 807(약 9.2×1018, 922경), unsigned의 경우 0~18 446 744 073 709 551 615(약 1.8×1019, 1844경)이다. [7]
꽤 많이 쓰이는 자료형이다.
6.4.6. __int128_t
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간) 까지의 범위를 가진다.6.5. 문자 자료형
여러 가지 자료형이 있다. 가장 기본이 되는 것은 char이며, 이외 자료형의 사용법을 아래 표에 간략히 정리하였다.자료형 | 리터럴 문법 | 필요 헤더 | 인코딩 | 컴파일러 의존성 | printf /scanf 호환 | 비고 |
char | "..." | 없음 | ASCII MBCS | 비의존 | O | 기본 자료형 |
u8"..." | uchar.h | UTF-8 | 비의존 | X | ||
wchar_t | L"..." | wchar.h | 와이드 문자(UCS-2/4) | 의존 | X[8] | |
TCHAR | _T("...") | tchar.h | MBCS 와이드 문자(UCS-2) | 비의존[9] | X[10] | 비표준 (Windows SDK) |
char16_t | u"..." | uchar.h | UTF-16 | 비의존 | X | |
char32_t | U"..." | uchar.h | UTF-32 | 비의존 | X | |
int | '...' | 없음 | ASCII | 의존 | X[11] | ISO/IEC 9899:TC3 §6.4.4.4 |
6.5.1. char
character를 줄인 단어이다. 어떤 사람은 '캐릭터'라고 읽기도 하고, 어떤 사람은 쓰여 있는 대로 '차'라고 읽는다.[12]6.5.1.1. 초기화 방법
변수에 데이터를 저장하는 방식이 조금 다르다.1. 문자를 사용
#!syntax cpp
#include <stdio.h>
int main()
{
char a;
a = 'a';
printf("%c", a);
return 0;
}
6줄과 같이 작은따옴표 사이에 한 글자짜리 문자(
'a'
)를 넣어서 변수에 저장하는 것이다. 다른 말로 하자면, 'a'는 (ASCII CODE 기준) 숫자 97을 다른 식으로 표현한 것이고, 따라서 변수 a에는 숫자 97이 저장된다. 2. 문자 코드를 사용
#!syntax cpp
#include <stdio.h>
int main()
{
char a;
a = 97;
printf("%c", a);
return 0;
}
컴퓨터에 저장되는 것은 사실 모든 것이 다 이진수이므로, 문자마다 숫자를 매겨서 그 숫자를 저장한다. 그러므로 1번이든 2번이든 a라는 문자가 아니라 a에 해당하는 문자 코드인 97이 저장된다.
3. 멀티시퀀스 리터럴
#!syntax cpp
#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 behavior다. [14]MSVC, Clang에서는 이 문법이 경고나 오류를 내지 않으나 GCC의 경우 경고를 출력한다.
#!syntax cpp
{
// Undefined behavior, 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 자료형에서 문자와 숫자 간의 매핑 관계 파악하기
#!syntax cpp
#include <stdio.h>
int main()
{
char a;
scanf("%c", &a);
printf("%d", a);
return 0;
}
뭔가 복잡할 것 같지만 은근 짧다. 원리는 간단하다. 문자를 입력받으면 아스키 코드로 저장되는데(6줄) 그것을 정수 형태로 출력(7줄)한 것이다. UTF-8 인코딩의 경우에는 영문 및 숫자 부분까지는 아스키 코드와 매핑이 같고 이외의 문자의 경우에는 다르다.
2.
#!syntax cpp
#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 자료형으로 입출력하는 예제이다.
#!syntax cpp
#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자의 바이트를 구하는 예제이다.
#!syntax cpp
#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 자료형이 나오기 전까지는 다국어 호환성이 뛰어나기 때문에 어느 정도 쓰였다. 그러나 플랫폼 의존적인 자료형이기 때문에 환경에 따라서 인코딩 결과가 달라진다는 점으로 인해 많은 개발자들이 사용하기 꺼리는 자료형이다.
printf / scanf 함수로 출력할 수 없으며 wprintf / wscanf 함수를 사용하여 출력한다. 그런데 로컬 설정의 영향을 받기에 영문자 외의 글자의 경우 아예 나오지 않거나 깨져서 나오는 문제가 있어서 로컬 설정을 해줘야 한다.
Windows API에서는 모든 문자열을 wchar_t로 처리하기에 십중팔구 윈도우 프로그래밍에서는 wchar_t를 사용해야 한다.
L"..."
리터럴을 사용한다. wchar.h
헤더를 include해야 한다.#!syntax cpp
#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해야 한다.#!syntax cpp
#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;
}
위와 같은 코드는
UNICODE
가 선언되어 있지 않으면 프리프로세서에서 다음과 같이 변환된다.#!syntax cpp
#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
가 선언되면 다음과 같이 변환된다.#!syntax cpp
#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::cin
및 std::cout
객체를 사용하지 않고서는 입출력이 거의 불가능하다.#!syntax cpp
#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::cin
및 std::cout
객체를 사용하지 않고서는 입출력이 거의 불가능하다.#!syntax cpp
#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] |
여기서 중요한 것은, 배열 번호(첨자라 한다)는 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차원... 의 배열을 통틀어 다차원 배열이라 한다.
사용 예시:
#!syntax cpp
#include <stdio.h>
int main()
{
int arr[5] = {1,2,3,4,5};
printf("%d", arr[2]);
}
출력 결과는 3이다. 첨자는 0부터를 반드시 기억하자.
6.9. 포인터
자세한 내용은 C(프로그래밍 언어)/포인터 문서 참고하십시오.포인터는 값 자체가 아니라 값이 저장된 주소를 담는 변수이다.
int *myPtr
와 같이 변수명에 *를 붙여서 표시한다.포인터 변수는 다음과 같이 선언한다. 띄어쓰기는 자유롭게 할 수 있다.
자료형* 변수이름; 혹은 자료형 *변수이름; |
주의할 점은 자료형에
*
를 붙인다고 포인터 변수가 되는 것이 아니라 변수명에 붙어야 유효하다.단순히 한개의 변수를 포인터로 선언할 때는 별 문제가 없지만 여러 변수를 포인터로 선언하는 경우
int* a, b, c;
가 있을 때 a
만이 int
의 포인터형이고 b
, c
는 단순 int
정수 자료형이다. 이 때문에 *
를 자료형이 아닌 변수명으로 붙여 쓰는 것이 권장된다. (위의 경우 올바른 표현법은 int *a, *b, *c;
가 된다.)또한, 포인터 변수에는 다음과 같이 주소를 저장한다.
변수의 이름 = &다른 변수의 이름; |
scanf
함수를 쓸 때 붙이는 &도 이것이다.마지막으로 포인터 변수가 가리키는 곳의 값을 편집할 때는 다음과 같이 쓴다.
*변수의 이름 = 넣을 값; |
6.9.1. 예시
#!syntax cpp
#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. 함수 포인터
포인터는 정수뿐만 아니라 함수를 가리킬 수도 있다.#!syntax cpp
#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. 복잡한 선언문 읽기
#!syntax cpp
int (**ptr[10])(void);
같은건 어떻게 읽어야 할까? 다음과 같이 아주 간단한 방법이 널리 알려져 있다.Reading C type declarations
변수이름부터 시작해서, 연산자 우선순위에 따라 연산자들을 결합해 나가며 영어로 읽으면 타입을 알 수 있다. 혹은 반대로, 가장 바깥쪽부터 시작해서 연산자 우선순위가 낮은 순서대로 한국어로 읽으면 된다.
6.9.4. 동적 할당
stdlib.h
또는 malloc.h
를 include하면 malloc
이라는 함수를 호출할 수 있는데, 이 함수를 이용해서 원하는 만큼의 공간을 가져와서 쓸 수 있다. 이 함수는 확보할 메모리의 크기가 런타임에 정해지는 경우에 주로 사용된다.malloc
은 새로 할당된 공간의 시작 주소를 반환하기 때문에, 이 주소를 저장할 포인터 변수가 필요하다. 문법은 다음과 같다.#!syntax cpp
ptr = (자료형*)malloc(할당할 크기);
할당할 크기는 byte 단위이다. 안전하게 사용하기 위해서는 할당할 크기를 자료형 크기의 정수 배로 하는 것이 좋다.
#!syntax cpp
int *ptr;
ptr = (int*)malloc(배열의 길이 * sizeof(int));
이렇게 할당하고 나면
ptr[5]
와 같이 사용할 수 있다.사실 동적 할당에서 형변환은 필요하지 않다. 아래처럼 쓰는 것이 보통이다.
#!syntax cpp
ptr = malloc(배열의 길이 * sizeof(int));
과거에는
void *
라는, 모든 포인터를 포괄하는 포인터 자료형이 없었기 때문에 malloc
의 반환 자료형은 char *
였고, 다른 타입의 변수에 대입하기 위해 명시적으로 형변환을 해야 했다. 이후 1989년에 void *
가 추가되고 malloc
의 반환 자료형이 void *
로 변경되면서 형변환이 필요 없게 되었다.그러나 예전부터 써온 관습도 있고, 컴파일러에 따라 경고 수준이 높을 경우 경고를 출력하는 데다, C++은 C와 다르게
void *
형에게도 명시적인 형변환을 요구하기 때문에, 대부분의 C 교재나 강의에서는 malloc
함수의 값을 형변환해서 쓰도록 가르치고 있다.[19]위의 예제에서
ptr
의 자료형이 변경된다면(예를 들어 int
에서 double
로) sizeof
의 피연산자도 바꿔야 하는 번거로움이 생긴다.(sizeof(int)
에서 sizeof(double)
로) 이 문제를 해결하는 근본적인 방법은 sizeof
의 피연산자를 자료형 대신 변수로 하는 것이다.#!syntax cpp
ptr = malloc (배열의 길이 * sizeof *ptr);
동적 할당된 공간은 자동으로 해제되지 않기 때문에, 더 이상 필요 없으면 프로그래머가 직접 해제해야 한다. 필요할 때마다 메모리를 계속 받으면서 해제하는 작업을 하지 않으면, 할당받았지만 접근할 수 없는 메모리 공간이 점점 늘어나면서 심할 경우 프로그램이 그대로 뻗는 경우도 있다.[20]
메모리 해제는
free
함수로 할 수 있다.#!syntax cpp
free (ptr);
6.10. 혼합형
6.10.1. struct
#!syntax cpp
struct [이름]
{
<포함될 멤버들>
};
여러 변수들을 하나로 묶어서 표현할 수 있다.
struct
을 typedef
과 함께 사용해서 독립적인 자료형으로 사용하는것도 가능하다.#!syntax cpp
typedef struct _MyStruct
{
int MyInt;
short MyShort;
} MyStruct;
MyStruct myVariable; // int가 4바이트, short가 2바이트일 때 MyStruct의 크기는 6바이트
[21]또한 멤버를 어레이처럼 struct를 생성과 동시에 초기화 하는것도 가능하다.
#!syntax cpp
[변수] = {.[멤버명]}
MyStruct mydata = {.MyInt = 1234, .MyShort=4321};
mydata.MyInt; // 1234
mydata.MyShort; // 4321
6.10.2. union
#!syntax cpp
union [이름]
{
<포함될 자료형들>
};
위의
struct
와 문법은 동일하며 typedef
을 통해 독립적인 자료형으로 사용하는것 또한 가능하지만 struct
가 멤버 변수들이 각각 메모리 영역을 할당받는것에 비해 union
은 가장 큰 자료형의 크기가 사용된다.#!syntax cpp
// int 가 4바이트고 short가 2 바이트일 때;
typedef union _MyUnion
{
int MyInt;
short MyShort;
} MyUnion;
MyUnion의 크기는 가장 큰 자료형인
int
의 4바이트가 된다. 이 특징을 통해 메모리를 비교적 적게 사용하면서 유연한 자료형을 만들 수 있으며 이를 응용하는것 또한 가능하다.#!syntax cpp
typedef union _MyUnion
{
int MyInt;
struct
{
short Low;
short High;
};
} MyUnion;
위
union
은 4바이트의 int
와 두개의 2바이트 자료형인 short
가 익명의 struct
으로 묶여 있으므로 4바이트이다. 이때 MyUnion.MyInt
에 0x12345678
가 들어 있을 때 MyUnion.High
는 0x1234
, MyUnion.Low
는 0x5678
를 포함하게 된다.MyUnion | |||||||
0x1 | 0x2 | 0x3 | 0x4 | 0x5 | 0x6 | 0x7 | 0x8 |
MyUnion.High | MyUnion.Low | ||||||
MyInt |
6.11. 형변환
#!syntax cpp
([자료형])[변환할 변수]
int MyInt = 1337;
unsigned int MyUnsignedInt;
MyUnsignedInt = (unsigned int)MyInt;
자료형을 변환한다.
특히 포인터 형변환을 통해 Opaque한 컨텍스트화 하거나 다른 자료형의 값을 변경하는 등의 응용이 가능하다.
#!syntax cpp
char* ptr;
int MyInt = 0x12345678;
ptr = (char *)&MyInt; // *ptr := 0x78
ptr++ // *ptr := 0x56
ptr++ // *ptr := 0x34
ptr++ // *ptr := 0x12
7. 함수
함수는 C언어에서 처리하는 명령이다. 기본적으로 이렇게 생겼다,함수 이름(입력 인자);[22] |
함수 원형부를 포함한 함수 바디라인은 이렇게 정의한다.
#!syntax cpp
[함수 지정자] [출력 자료형] [함수 이름](입력 인자)
{
[함수 내용]
}
예를 들어, 두 수를 입력 받아 그 수의 합을 돌려 주는 함수는 이렇게 생겼다.
#!syntax cpp
int sum(int a, int b)
{
return a+b;
}
위 코드에서 알 수 있듯이, return은 그 뒤에 있는 값을 결과물로 내보내고, 함수를 끝낸다.
한편, 함수에서 return한 값을 받는 방법은 다음과 같다.
#!syntax cpp
int num;
num = sum(3,5);
이 경우,
num
에는 8이 저장된다.GCC C99 기준 자료형을 명시하지 않을 경우 int로 처리되며 return 문이 없으면 쓰레기 값을 반환한다.
함수 지정자는 필수가 아닌 옵션이며 함수의 특성을 지정한다. 여기에는
inline
, static
, extern
이 포함된다.#!syntax cpp
extern void MyExternFunction(){...} // 소스코드 내에 함수가 정의되어 있다면 해당 함수의 심볼이 바이너리의 export 테이블에 노출되며 .dll, .so와 같은 라이브러리의 형태를 띄고 있을 경우 외부에서 해당 함수에 접근이 가능하다. 만약 함수의 내용이 정의되어 있지 않고 형태만 선언되어 있다면 외부에서 심볼을 찾아서 링크한다.
static void MyStaticFunction(){...} // 스태틱 함수를 선언한다. static 함수는 외부에서 접근하지 못한다.
inline void MyInlineFunction(){...} // [[인라인 함수]]를 선언한다. Static과 달리 컴파일러가 상황에 따라서 해당 함수의 내용을 별도의 스택프레임을 생성하지 않고 한 코드에 포함시킨다.
7.1. main 함수
main 함수는 프로그램의 진입점으로, 프로그램이 실행되면 자동으로 호출되는 함수이다. 따라서 한 프로그램당 하나만 정의할 수 있으며, 일반적으로는 이렇게 작성한다.#!syntax cpp
int main()
{
[실행할 코드]
return 0;
}
main 함수에 입력 인자를 받는 경우에는 다음과 같이 정의한다.
#!syntax cpp
int main(int argc, char* argv[], char* envp[])
{
[함수 내용]
return 0;
}
argc는 컴파일된 프로그램이 받는 매개변수의 개수이고, argv[]는 매개변수 문자열들의 배열이다. argv[]는 argv[argc]를 의미한다. 배열 argv[]의 첫번째 원소
argv[0]
은 주로 프로그램 자신의 이름이다.[23] 입력받는 매개변수의 문자열들은 자동적으로 원소 argv[1]부터 원소 argv[argc-1]까지 입력받은 순서대로 저장된다.비표준 구현인 envp[]는 운영체제 환경변수 문자열들의 배열이다. argv[]는 자주 쓰이나 envp[]는 거의 쓰이지 않고 main 함수의 매개인자에서 생략되는 경우도 많다.
argc
, argv
와 마찬가지로 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.1.1. wmain 함수
main 함수의 유니코드 버전이다. 차이점은 argv, evp 배열의 자료형이 wchar_t*인 점만 빼면 main 함수와 동일하다. 당연하지만 main 함수와 같이 사용할 수 없다.#!syntax cpp
int wmain(int argc, wchar_t* argv[], wchar_t* envp[])
{
[함수 내용]
return 0;
}
7.2. 내장 연산자
C언어에서 아무런 헤더파일도 선언하지 않고 쓸 수 있다. 대부분의 내장 연산자는 함수와는 다른 형태로 쓰지만,sizeof()
연산자는 하나의 변수를 괄호 안에 넣는 함수 형태로 쓰인다.7.3. 헤더 파일
헤더 파일은 이미 있는 함수들의 모임이다. 프로그램의 가장 앞에 선언하면 그 안의 함수를 쓸 수 있다. 문법은 다음과 같다.#!syntax cpp
#include <헤더 파일 이름>
7.4. 표준 라이브러리 함수 일람
C언어와 사실상 필수적으로 사용되는 표준 라이브러리의 목록이며 이는 C2x 표준을 기준으로 작성되었다. [24]Windows.h
나 unistd.h
와 같이 여기에 존재하지 않는 헤더는 모두 플랫폼의 확장들이다.표준 라이브러리는 어느 운영체제에서든 사용할 수 있는 라이브러리를 의미한다. 이를 통해 특정 운영체제에 종속되지 않고 동일한 코드로 여러 플랫폼으로 컴파일해서 사용할 수 있다. 표준 라이브러리의 함수는 최종적으로 타겟 운영체제의 API 호출로 이루어진다.
- assert.h
- complex.h
- ctype.h
- errno.h
- fenv.h
- float.h
- inttypes.h
- iso646.h
- limits.h
- locale.h
- math.h
- setjmp.h
- signal.h
- stdalign.h
- stdarg.h
- atdatomic.h
- stdbool.h
- stddef.h
- stdint.h
- stdio.h
- stdlib.h
- stdnoreturn.h
- string.h
- tgmath.h
- thread.h
- time.h
- uchar.h
- wchar.h
7.4.1. assert.h
Assertion. 디버그시 사용된다. 주로 런타임 디버그시 사용하며 컴파일시NDEBUG
가 선언되어 있지 않고 조건을 만족하지 못했다면 런타임에서 오류가 출력된다. 이때 동작하는 방식은 런타임 라이브러리마다 다르지만 보통 런타임 오류와 해당 라인과 소스코드 파일명과 조건식을 출력하고 종료된다. Windows 시스템의 경우 디버그 가능한 툴이 설치되어 있다면 (drmingw, MSVC) 런타임 라이브러리에서 해당 프로세스에 디버거를 붙이는 옵션을 제공하기도 한다.#!syntax cpp
assert([조건식]);
#ifdef NDEBUG
#undef NDEBUG
#endif // NDEBUG
#include <assert.h>
#include <stdio.h>
int main(int argc, const char* argv[])
{
assert(argc > 1); // argc가 1보다 작다면 런타임 라이브러리에서 오류 메세지를, 그렇지 않으면 그대로 진행한다.
return 0;
}
7.4.2. 입/출력 함수 (stdio.h)
7.4.2.1. printf
int printf(const char *format, ...)
printf
는 문자열(문장) 이나 변수를 화면에 출력하는 함수이다. 여기서 f는 format으로, 형식을 줘서 출력한다는 뜻이다. printf
함수는 리터럴과 출력에 들어갈 변수의 두 가지를 입력 받는다.7.4.2.1.1. 기본 문법
printf
의 기본 문법은 다음과 같다.printf(리터럴[문자열 + 서식 지정자], 변수1, 변수2, ...);
이때 서식 지정자(format specifier)가 있는 자리에 변수가 차례로 들어간다.
예를 들어, a가 4라면,
printf("a의 값은 %d 입니다.", a);
a의 값은 4 입니다. |
%d
가 서식 지정자이다. 옵션과 변수의 위치가 바뀌는 셈.7.4.2.1.2. 리터럴
ASCII 인코딩의 char 자료형은 리터럴이 필요없으나, char 자료형을 UTF-8 인코딩으로 출력하는 경우에는 u8 리터럴을 사용해야 한다.wchar_t 자료형에서는 L"..." 리터럴을, tchar 자료형에서는 _T(...) 리터럴을 사용한다.
7.4.2.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.2.2. scanf
int scanf(const char *format, ...)
scanf
는 값을 입력받는 함수이다. scanf
함수는 옵션과 입력값을 저장할 변수의 포인터, 즉 그 변수의 주소 두 가지를 입력 받는다. 쉽게 설명하기 위해 택배에 비유하자면 입력값은 택배 내용물, 변수의 주소 값은 택배에 쓸 주소이다.보안상 문제로 비쥬얼 스튜디오 2012 까지는
scanf
를 사용해도 경고가 나올 뿐 문제가 없었으나, 그 다음 버전부터는 아예 실행이 불가능하게 바뀌었다. 입력을 받거나 파일을 불러오는 코드를 사용할 시 scanf_s
를 사용하거나 두 번째 줄에 _CRT_SECURE_NO_WARNINGS를 추가해주어야 한다. 소스 파일 속성 > C/C++ > 일반 설정으로 들어가서 SDL 검사를 '아니오'로 설정해줘도 된다.7.4.2.2.1. 서식 지정자
printf
와 같은 서식 지정자를 쓰지만, *의 의미가 '해당하는 값 하나를 읽은 다음 저장하지 않고 버리는 것'으로 바뀌는 등 소소한 변화가 있다. 예를 들어 scanf("%*d %d", &a);
에서 3 5
를 입력받았을 경우 3을 버리고 a
에 5를 저장한다.printf함수의 서식 지정자와 비교해보면, scanf는 입력받는 매개변수의 타입에 좀 더 신경을 써서 서식 지정자를 지정해 주어야 한다. printf함수에서는 가변함수의 기본인자진급 규칙으로 인해 int형과 double형 위주로 '대충'[25] 값을 넘겨받는데 비해 scanf함수는 포인터 주소값을 매개변수로 넘겨받은 뒤 표준입출력으로 입력받은 값을 포인터 주소값 위치에 직접 써 주기 때문에 꼼꼼히 신경써서 지정해 주지 않으면 잘못된 메모리 공간에 대해 쓰기를 시도할 수 있다.
서식 지정자 이외의 다른 문자열도 넣을 수 있는데, 이렇게 하면 다른 문자열을 읽은 이후에 서식 지정자에 해당하는 값을 읽어온다. 예를 들어
scanf("abc%d", &a)
처럼 코딩하고 입력값으로 abc10을 넣으면 abc를 읽어온 후에 버리고 10을 읽는다. abcd, a1000 같은 문자열을 입력한 결과는 정의되지 않았기 때문에 주의해야 한다. scanf("%d, %d", &a, &b)
로 코딩하고 3 5
를 입력하면 a에는 3이 정확히 들어가지만 b에는 값이 들어가지 않는다. 3, 5
로 입력해주어야 한다.7.4.2.3. 파일 입출력
7.4.2.3.1. fopen, fclose, fread, fwrite
#!syntax cpp
FILE* fopen(const char* [파일 경로], const char* [모드]);
파일 경로에 입력된 파일을 열고 성공했다면 0이 아닌 값을 반환한다. 보통 파일명만 적는 경우 OS의 정책에 따르지만 보통 작업 경로 (pwd)를 사용하게 되며 전체 경로를 명시적으로 지정해도 된다.
사용 가능한 모드는 다음과 같다.
r
읽기 - 파일이 없으면 오류w
쓰기 - 파일이 없으면 생성 있으면 덮어쓰기a
이어쓰기 - 파일이 없으면 새로 생성, 그렇지 않다면 파일의 끝부분 부터 이어쓰기하게 된다.r+
읽고 쓰기 - 파일이 없으면 오류w+
읽고 쓰기 - 파일이 없으면 생성 있으면 덮어쓰기a+
이어쓰기 - 파일이 없으면 새로 생성, 그렇지 않다면 파일의 끝부분 부터 이어쓰기하게 된다.
모든 C 런타임 라이브러리가 그렇듯이 모든 파라미터는 시스템의 로캘을 따른다. 그렇기 때문에 영어 로캘 환경에서는 한글이 포함된 경로에 액세스 할 수 없으며 한국어 로캘 환경에서는 일본 한자와 가나가 포함된 경로를 읽을 수 없다.
이 때문에 Windows환경에서는
wchar
로 된 wfopen()
을 제공하고 있으나 표준이 아니므로 크로스플랫폼 환경에서는 사용할수 없다.#!syntax cpp
int fclose(FILE*);
fopen()
이 연 파일 객체를 닫는다. OS의 API가 제공하는것과 달리 C라이브러리의 파일 핸들은 프로세스가 락을 걸어 다른 프로세스가 액세스할 수 없으므로 핸들을 닫아줘야 다른 프로세스가 엑세스 하는것이 가능하다.#!syntax cpp
size_t fread(void* [버퍼], size_t [크기], size_t [갯수], FILE* [파일 객체]);
size_t fwrite(const void* [버퍼], size_t [크기], size_t [갯수], FILE* [파일 객체]);
파일을 읽어서 버퍼에 쓰거나 버퍼의 내용을 파일에 쓴다.
예시:
#!syntax cpp
#include <stdio.h>
const char *DefaultFilePath = "Untitled.txt";
const char DefaultString[] = "Hello World!";
FILE* MyFile;
int main()
{
char buffer[32];
FILE *MyFile;
MyFile = fopen(DefaultFilePath, "w+"); // 파일을 연다.
if (!MyFile) // 위에서 filename을 열거나 생성하지 못했으면 NULL을 반환하므로
// 이를 확인한다.
{
printf("Unable to create file: %s\n", DefaultFilePath);
return 1;
}
fwrite(
DefaultString, // 버퍼
sizeof(DefaultString) - 1, // 크기
1, // 갯수
MyFile // 파일
); // MyFile에 DefaultString의 sizeof(DefaultString) - 1 만큼의 내용을 쓴다.
fseek(
MyFile,
0,
SEEK_SET); // 파일에 기록될때 순차적으로 기록되어 커서가 맨 뒤로 가 있는
// 상태이다. 파일을 읽기 위해 커서를 맨 앞으로 가져온다.
fread(
buffer,
sizeof(DefaultString) - 1 > BUFFERSIZE ? BUFFERSIZE - 1
: sizeof(DefaultString) - 1,
1,
MyFile); // 파일을 읽어 buffer에 쓴다.
fclose(MyFile); // 파일 객체를 닫는다.
printf("%s\n", buffer); // 파일을 읽을때 사용한 버퍼의 내용을 출력
}
8. 반복문
8.1. for
for(exp1; exp2; exp3){(statement)}
우선 exp1을 수행한다. 그 이후 exp2가 참인 동안 statement를 수행하고 exp3를 수행한다.
즉, 아래 구문과 완전히 동일하다.
for(;;)
이라고 쓰면 무한루프를 돈다.#!syntax cpp
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
#!syntax cpp
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문을 사용한 홀짝 판별법.
#!syntax cpp
#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의 예제로 두 정수와 연산자를 하나 입력했을 때, 두 수를 후에 입력한 연산자대로 계산하는 코드.
#!syntax cpp
#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 ...
#!syntax cpp
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. 기타 표준 라이브러리의 기능
11.1. stdbool.h
C++에서처럼 1바이트 크기의 bool 자료형을 사용할 수 있게 해 준다. 그리고 '0 아닌 수'와 0 대신 true와 false로 값을 적을 수 있다.12. 관련 문서
[1] 예약어/Reserved Word라고도 한다.[참고] a ^ (b ^ a) == (a ^ b) ^ a == b[참고2] a ^ b == b ^ a[4] 즉 실행시간에 서로 다른 데이터형을 결과값으로 가지는 수식은 만들 수가 없다.[5] 실제로 TI Piccolo C28x와 같은 특정 아키텍쳐에서는 1byte=16bits이다. 이는 농담이나 흘러간 옛 추억이 아니며, 누군가는 지금도 머리 깨지며 마주하고 있을 엄연한 현실이다![6] 표현할 수 있는 자리수도 길고 글자수도 길다.....[7] 210=1024는 103=1000에 근사하므로 263은 천-백만-십억-조-천조-백경에 23를 곱하여 대충 800경 근처로 근사할 수 있다(실제는 약 922경). 큰 수라 해도 쓰다보면 오버플로우가 안나는 것은 아니기 때문에 표현범위는 어떠한 데이터형이 되었든간에 확인해 두는 것이 바람직하다. 경우는 조금 다르지만 온라인 게임에서 실제로 해당 범위에서 오버플로가 나는 경우가 발생하기도 한다.[8] wprintf/wscanf와 호환된다.[9] TCHAR 문단 참조. 컴파일러의 확장이 아닌 단순 매크로이다.[10] tprintf/tscanf와 호환된다.[11] 컴파일러에 따름[12] 캐릭터에서 줄여진 말이므로 카 혹은 케어라고 읽는것이 맞겠지만, 왜인지 국내에선 차로 널리 알려져 있다. char의 발음에 대한 혼란은 외국에서도 동일해서 당장 유튜브나 구글에 char 발음을 검색하면 카/케어/차/샤 넷중에 무엇으로 읽어야하는지 혼란스러워하는 사람들이 나온다.[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] 이제 C++에서는 malloc/free 함수보다 new/delete 연산자 사용을 권장한다.[20] 그러면 malloc 함수가 NULL을 반환하므로 꼭 확인하자[21] 위 Struct를 일부 컴파일러에서 컴파일 하는 경우 멤버 접근 속도 최적화를 위해 자료형과 관계 없이 padding을 붙여 8바이트로 할당 될 수 있으며 이 경우 별도의
#pragma
를 사용해서 struct의 멤버에 padding 이 붙는것을 방지하여야 한다.[22] ;때문에 c언어에 익숙하지 않은 사람들이 많이 고통을 받는다. int main(); 이라는 말도 생겼을 정도.[23] exec()
(유닉스)나 CreateProcess()
(윈도우)와 같은 방식으로 프로세스가 시작되는 경우 argv[0]
가 항상 프로세스 파일명이 아닐 수 있다.[24] ISO/IEC 9899:202x (draft) § 7.1.2 Standard headers[25] 좀 더 정확히는, char형이나 short형은 int형으로, float형은 double형으로 변환되어 넘어간다