ISO C와 ISO C++의 차이
번역 소개글
이 글은 David R. Tribble씨의 허락을 얻어 번역한 글입니다.
영문 원본은 Incompatibilities Between ISO C and ISO C++을 보기 바랍니다.
저작권
이 번역본에 대한 저작권은 본인, 신성국(Seong-Kook Shin)에게 있습니다.
번역자 이름을 언급한다는 전제로, 번역자의 허락없이 이 번역본의 일부 또는 전체를 사용하거나 인용할 수 있습니다. 마찬가지로 번역자의 이름을 언급하고, 이 저작권 안내문이 변경되지 않고 출력된다는 전제로, 이 문서를 번역자의 허락없이 출력하고 배포할 수 있습니다.
소개글
ANSI X3J9 위원회는 C 언어에 대한 표준화 작업을 1985년 안팍에 시작했고, 몇년이 지나서 1989년 ANSI에 새 표준이 등록되었습니다.
이 후, 1990년에 ISO 위원회는 국제화 이슈를 추가한 개정판amendment을 만들고 이를 ISO C 표준으로 만들었습니다. 1989년 C 표준의 공식 이름은 ANSI/ISO 9899-1989, Programming Lanaguges - C이며, 이 글에서는 간단히 C89라고 부르겠습니다. 1990년 ISO 개정 표준의 공식 이름은 ISO/IEC 9899-1990, Programming Languages - C이며, 이 글에서는 C90이라고 부릅니다.
ISO 위원회는 1999년에 새 버전을 내놓았고, 이 문서의 공식 이름은 /ISO/IEC 9899-1999, Programming Lanaguges - C/이며, 이 글에서는 C99라고 부릅니다.
C++ 언어는 ANSI C 이후의 C 언어를 기반으로 하고 있습니다. 1995년 즈음에 ISO 위원회에서 C++를 표준화하려는 움직임이 있었고, 1998년에 ISO/IEC 14882-1998, Programming Languages - C++이라는 이름의 표준이 만들어졌습니다. 이 표준 문서는 이 글에서 C++98 또는 C++이라고 부르겠습니다.
두 언어가 공통 조상을 가진 것은 맞습니다. 그리고 표준화 작업 과정에서, 관련자들은 가능하면 두 언어의 호환성을 유지하려고 노력했지만, 불가피하게 약간의 차이가 생겼습니다. 따라서 이 차이점들을 알아두면, C 코드를 작성할 때 문제가 발생할 일이 줄어들 것입니다.
일반적으로 "특정 기능에 대해 C 언어가 C++과 호환이 되지 않는다incompatible C features"라는 말은, (1) 해당 기능을 쓴 C 프로그램이 (C++ 문법에 어긋나서) 틀리다not valid라는 뜻이거나, (2) 컴파일은 되지만, C 언어에서와 다른 방식으로 동작한다different behavior라는 뜻입니다. 즉, C 언어로는 올바른 프로그램이지만, C++ 언어로서는 타당하지 않다는 뜻입니다. 이 글에서는 이러한 차이점들을 모두 다루려고 합니다. 이러한 차이점들을 잘 피해간다면, C++ 코드로 컴파일해도, 무리가 없는 C 코드를 작성하는데 많은 도움이 될 것이라 생각합니다.
다른 차이로, C++ 프로그램으로는 올바르지만, C 언어로는 타당하지 않은 기능들이 있습니다. 이 글에서는 이러한 기능들을 "호환성없는 C++ 기능incompatible C++ features"이라고 부를 것입니다. C++ 언어의 많은 부분이 여기에 해당합니다. (예를 들어 class, template, exception, reference, member function, anonymous union등이 있습니다.) 이 글에서 이러한 부분들은 자세히 다루지 않을 것입니다.
또 다른 차이로, C90과 C++이 서로 같은 이름의 기능을 제공하지만, 이 기능이 언어에 따라 차이가 있는 경우가 있습니다. 이러한 내용들은 이 글에서 다룰 것입니다.
이 글은 C99와 C++98의 차이를 다룹니다. (C90과 C++의 차이는 다루지 않습니다. 자세한 것은 부록 B의 Stroustrup [STR]을 보기 바랍니다.)
또 이 글에서, C99 표준 라이브러리에 추가된 내용들은, C++과 차이가 있지 않은 한, 다루지 않을 것입니다.
C++ vs. C
앞 소개에서 간단히 말했지만, 호환성 없는 C++ 기능, 즉, C 언어에서는 쓸 수 없는 C++ 특성들은 이 글에서 다루지 않을 것입니다. C++ 언어의 많은 부분이 여기에 해당하며, C++ 표준 라이브러리도 여기에 해당합니다. 이러한 기능들에는 다음과 같은 것들이 있습니다. (모든 기능이 나열된 것은 아닙니다.)
- anonymous union
- class
- constructor, destructor
- exception 그리고
try/catch
block - external function linkage (예:
extern "C"
) - function overloading
- member function
- namespace
-
new
와delete
연산자 - operator overloading
- reference type
- standard template library (STL)
- template class
- template function
C++98과 같은 C99 특징
이 단원에서는, C90 때에는 C++98과 호환성이 없었지만, C99에서는 호환성이 있는 내용들에 대해 다룹니다.
합성 타입 초기값aggregate initializer
C90에서는, 합성 타입aggregate type(struct, 배열, union 등)이 automatic, register 변수로 쓰일 때, 상수constant expression만 초기값으로 쓸 수 있습니다. (그러나 대부분 컴파일러들이 이 제한을 지키고 있지 않습니다.)
C99에는 이러한 제한이 없습니다.
C++에서는, automatic/register 변수 초기값으로 상수가 아닌 표현non-constant expression도 쓸 수 있습니다. (또, 상수가 아닌 표현을 static/external 변수 초기값으로도 쓸 수 있습니다.)
예를 들면 다음과 같습니다:
// C and C++ code
void foo(int i)
{
float x = (float)i; // C90, C99, C++ ok
int m[3] = { 1, 2, 3 }; // C90, C99, C++ ok
int g[2] = { 0, i }; // C90
}
[C99: §6.7.8] [C++98: §3.7.2, 8.5, 8.5.1]
주석comment
C++에서는 /* ... */
형태 이외에 //...
형태의 주석comment을
쓸 수 있습니다.
C90은 /* ... */
형태의 주석만 인식합니다. 일반적으로 //...
형태의
주석을 쓰면 문법 에러가 발생하지만, 드물게 경고없이 원하지 않은 뜻으로
컴파일되는 경우가 있습니다:
i = (x//*y*/z++
, w);
C99는 두 가지 형태의 주석을 모두 인식합니다.
[C99: §5.1.1.2, 6.4.9] [C++98: §2.1, 2.7]
조건문에서 선언conditional expression declaration
C++에서는, 조건문conditional expression에 지역 변수를 선언할 수 있습니다.
(예: for
, if
, while
, switch
문장에서.) 이렇게 선언된 지역 변수는
조건문을 포함한 문장statement의 끝까지 scope를 가집니다. 예를 들면 다음과
같습니다:
for (int i = 0; i < SIZE; i++)
a[i] = i + 1;
C90은 이 특성을 제공하지 않습니다.
C99는 이 특성을 제공하지만, for
문장에서만 쓸 수 있습니다.
[C99: §6.8.5] [C++98: §3.3.2, 6.4, 6.5]
이중글자digraph 토큰
C++ 언어는 두 글자로 이루어진 토큰token을 지원합니다. 이것을 'digraph'라고 부르는데, C90에서는 지원하지 않습니다. 이 두 글자 토큰과 실제 해당하는 토큰은 다음과 같습니다:
<: |
[ |
:> |
] |
<% |
{ |
%> |
} |
%: |
# |
%:%: |
## |
C99는 C++과 같이 위의 두 글자 토큰을 지원합니다.
따라서 아래 프로그램은 C99와 C++에서 다 쓸 수 있습니다.
%:include <stdio.h>
%:ifndef BUFSIZE
%:define BUFSIZE 512
%:endif
void copy(char d<::>, const char s<::>, int len)
<%
while (len-- >= 0)
<%
d<:len:> = s<:len:>;
%>
%>
[C99: §6.4.6] [C++98: §2.5, 2.12]
함축적 함수 선언implicit function declaration
C90은 함수가 호출되기 전에 선언되지 않은 경우, 이 함수는
'함축적으로implicitly 선언되었다'라고 하며, 해당 함수는 int
를
리턴하는 것으로 간주합니다. 예를 들면 다음과 같습니다:
/* scope bar() */
void foo(void)
{
bar(); /* : extern int bar() */
}
C++에서는 이러한 함축적 선언을 허용하지 않습니다. 따라서 해당 scope에 선언이 없을 경우, 그 함수를 부를 수 없습니다.
C99도 함수가 함축적으로 선언된 것을 허용하지 않습니다. 따라서, 위의 코드는 C99와 C++ 모두에서 틀린 코드입니다.
[C99: §6.5.2.2] [C++98: §5.2.2]
함축적 변수 선언implicit variable declaration
C90에서 변수 선언, 함수 인자, 구조체 멤버에 타입 지정자type specifier가
없을 경우, int
로 선언된 것으로 간주합니다.
C99와 C++은 모두 이러한 생략을 허용하지 않습니다.
따라서 아래 코드는 C90에서는 올바르지만, C99와 C++에서는 틀린 코드입니다:
static sizes = 0; /* Implicit int, error */
struct info
{
const char * name;
const sz; /* Implicit int, error */
};
static foo(register i) /* Implicit ints, error */
{
auto j = 3; /* Implicit int, error */
return (i + j);
}
[C99: §6.7, 6.7.2] [C++98: §7, 7.1.5]
선언과 문장 순서intermixed declarations and statements
C90 문법에서는, 모든 선언은 해당 블럭의 첫번째 문장statement이 나오기 전에 미리 나와야 합니다.
C++에서는 이러한 제한이 없습니다. 즉, 문장statement과 선언이 섞여 쓰여도 됩니다.
C99에서도 이러한 제한이 없기 때문에, 문장statement과 선언을 섞어 쓸 수 있습니다.
void prefind(void)
{
int i;
for (i = 0; i < SZ; i++)
if (find(arr[i]))
break;
const char *s; /* C90 , C99 C++ ok */
s = arr[i];
prepend(s);
}
[C99: §6.8.2] [C++98: §6, 6.3, 6.7]
C++98과 다른 C99 특징
이 단원에서는 C99와 C++98의 차이점에 대해서 다룹니다. 이 차이점 중에는 C89 시절부터 존재했던 것들도 있고, C99로 개정되면서 생긴 차이점들도 있습니다.
앞에서도 잠깐 말했지만, C++에 있는 특성(예: class member function)들은 이 단원에서 다루지 않을 것입니다. 단지, C와 C++에 모두 존재하는 특성들 중 차이가 있는 것들만 다룹니다. 이 차이점들은 대부분 C에서는 올바르지만 C++에서는 틀린 것들입니다.
몇몇 특성들은, C 언어와 호환성을 높이기 위해, C++ 컴파일러의 확장 기능으로 제공되기도 합니다.
대체 가능한 토큰alternate punctuation token spellings
C++에서는 구두점 토큰punctuation token 대신에 다음 토큰들을 쓸 수 있습니다:
<table border="2" cellspacing="0" cellpadding="6" rules="groups" frame="hsides"> <colgroup><col class="left" /><col class="left" /> </colgroup> <tbody> <tr><td class="left"><code>and</code></td><td class="left"><code>&&</code></td></tr> <tr><td class="left"><code>andeq</code></td><td class="left"><code>&=</code></td></tr> <tr><td class="left"><code>bitand</code></td><td class="left"><code>&</code></td></tr> <tr><td class="left"><code>bitor</code></td><td class="left"><code>|</code></td></tr> <tr><td class="left"><code>compl</code></td><td class="left"><code>~</code></td></tr> <tr><td class="left"><code>not</code></td><td class="left"><code>!</code></td></tr> <tr><td class="left"><code>noteq</code></td><td class="left"><code>!=</code></td></tr> <tr><td class="left"><code>or</code></td><td class="left"><code>||</code></td></tr> <tr><td class="left"><code>oreq</code></td><td class="left"><code>|=</code></td></tr> <tr><td class="left"><code>xor</code></td><td class="left"><code>^</code></td></tr> <tr><td class="left"><code>xoreq</code></td><td class="left"><code>^=</code></td></tr> </tbody> </table>
위 키워드들은 C++ 전처리기preprocessor에서도 인식합니다.
C90에서는 (역자 주: 사실상 C89/C90/C99 모두) 위 키워드들을 제공하지
않습니다. 대신 위 이름들을 <iso646.h>
헤더 파일에서 (C90부터)
매크로로 제공하기 때문에, 키워드처럼 쓸 수 있긴 합니다.
C++ 언어는 비어있는 <iso646.h>
헤더를 제공하기 때문에,
C++ 프로그램에서 이 헤더를 포함해도 아무런 문제가 없습니다.
이 헤더를 포함하지 않은 C 프로그램에서는, 위 이름들을 다른 목적으로
써도 됩니다. 대신, 이럴 경우, C++ 프로그램으로 컴파일하면
문제가 발생합니다.
enum oper { nop, and, or, eq, ne };
extern int instr(enum oper op, struct compl *c);
따라서 작성한 프로그램이 C와 C++ 모두 지원하려면, 위의
이름identifier들을 다른 목적으로 쓰지 말아야 하며, 위 이름들을
쓸 때, 무조건 <iso646.h>
를 포함시키는 것이 좋습니다.
// Proper header inclusion allows for the use of 'and' et al
#ifndef __cplusplus
#include <iso646.h>
#endif
int foo(float a, float b, float c)
{
return (a > b and b <= c);
}
[C99: §7.9] [C++98: §2.5, 2.11]
배열 파라미터 한정사{array parameter qualifier}
C99에서는, 함수 인자로 배열이 올 때, 배열 선언부의 첫
대괄호bracket, ([]
) 안에 타입 한정사qualifier (예: cv-qualifier인
const
, volatile
, restrict
)를 쓸 수 있습니다.
이 한정사qualifier는 배열 파라미터의 타입을 변경합니다. 따라서 아래 두 선언은
같은 뜻입니다:
extern void foo(int str[const]);
extern void foo(int *const str);
위 두 선언에서 파라미터 str
은 int
오브젝트를 가리키는 const
포인터입니다.
C99에서는, 또, 배열 선언부에서 배열 크기를 나타내는 수식expression 앞에
static
지정자specifier를 쓸 수 있습니다. 이 경우, 배열의 크기가 적어도
지정된 배열 크기보다 같거나 클 것을 나타냅니다. (즉, 컴파일러에게 배열을
효과적으로 다룰 수 있도록 힌트를 제공합니다.) 예를 들면 다음과 같습니다:
void baz(char s[static 10])
{
// s[0] s[9]
...
}
이 모든 기능들은 C++에서 제공하지 않습니다.
(몇몇 C++ 컴파일러에서는 확장 기능으로 제공할 수도 있습니다.)
[C99: §6.7.5, 6.7.5.2, 6.7.5.3] [C++98: §7.1.1, 7.1.5.1, 8.3.4, 8.3.5, 8.4]
불리언 타입boolean type
C99는 _Bool
키워드를 제공하며, 이는 참/거짓을 나타내는
정수integer 타입을 선언하는데 씁니다. 또, <stdbool.h>
헤더를
제공하며, 아래와 같은 매크로를 제공합니다:
bool |
_Bool 과 같음 |
false |
(_Bool)0 과 같음 |
true |
(_Bool)1 과 같음 |
C++에서는 bool
, false
, true
가 모두 키워드이며, bool
은
내재된built-in 불리언boolean 타입입니다.
<stdbool.h>
를 포함하지 않은 C 프로그램은, 이 키워드들을 다른
이름identifier이나 매크로 이름으로 쓸 수 있지만, 이럴 경우 C++
프로그램으로 컴파일하면 문제가 됩니다. 예를 들면 다음과 같습니다:
typedef short bool; // Different
#define false ('\0') // Different
#define true (!false) // Different
bool flag = false;
따라서, C 프로그램에서도, 위 이름들을 다른 목적으로 쓰는 것은 좋지 않으며,
올바른 목적으로 쓸 경우, 반드시 <stdbool.h>
을 포함시켜야 합니다.
(대부분 C++ 컴파일러는 확장 기능으로서, 비어있는 <stdbool.h>
헤더를
제공합니다.)
[C99: §6.2.5, 6.3.1.1, 6.3.1.2, 7.16, 7.26.7] [C++98: §2.11, 2.13.5, 3.9.1]
문자 상수character literal
C 언어에서, 'a
'와 같은 문자 상수는 int
타입이며, 따라서 sizeof('a')
는
sizeof(int)
와 같습니다.
C++에서 문자 상수는 char
타입이며, 따라서 sizeof('a')
는
sizeof(char)
와 같습니다.
따라서 C와 C++ 프로그램으로 컴파일할 때, 다르게 동작하는 코드가 나올 수 있습니다.
memset(&i, 'a', sizeof('a')); // Questionable code
사실상, 이는 큰 문제가 되지 않습니다. 왜냐하면, C와 C++ 모두에서,
문자 상수가 수식expression에 나올 경우, 함축적으로implicitlyint
타입으로 변경되기 때문입니다.
[C99: §6.4.4.4] [C++98: §2.13.2]
clog
이름
C99에서, <math.h>
헤더에 복소수complex 자연
로그natural logarithm 함수로 clog()
를 제공합니다.
C++은 <iostream>
헤더에 표준 에러 로그
스트림standard error logging output stream으로 std::clog
를
제공합니다. (이는 stderr
스트림과 같습니다.)
clog
은 <math.h>
헤더가 포함될
경우, 전역global namespace에 위치하며, 로그 함수를
가리킵니다. 만약 <math.h>
헤더가 clog
를 매크로 이름으로
정의했다면, C++ 코드와 충돌날 수 있습니다.
// C++ code
#include <iostream>
using std::clog;
#include <math.h> //
void foo(void)
{
clog << clog(2.718281828) << endl;
// Possible conflict
}
C++ 프로그램에서 이런 충돌 가능성을 없애려면, <iostream>
과
<cmath>
헤더를 포함하면 됩니다. 이 경우, 두 clog
이름이 모두
std::
namespace에 존재하며, 하나는 변수 이름이고 다른 하나는 함수
이름이기 때문에 문제가 발생하지 않습니다.
// C++ code
#include <iostream>
#include <cmath>
void foo(void)
{
std::clog << std::clog(2.718281828) << endl;
//
}
void bar(void)
{
complex double (* fp)(complex double);
fp = &std::clog; //
}
이 충돌 가능성을 회피하기 위해, 한 소스 파일 안에서 서로 다른 뜻의 clog
를
동시에 쓰지 않는 것도 좋습니다.
[C99: §7.3.7.2] [C++98: §27.3.1]
콤마 연산자 결과
C 언어에서 콤마 연산자는, 오른쪽 피연산자가 l-value이더라도, 결과는 r-value가 나옵니다. C++에서는 오른쪽 피연산자가 l-value일 경우, 결과도 l-value가 됩니다. 따라서 아래 예는 올바른 C++ 코드이지만 C 프로그램으로서는 틀린 코드가 됩니다:
int i;
int j;
(i, j) = 1; // C++ ok, C
[C99: §6.5.3.4, 6.5.17] [C++98: §5.3.3, 5.18]
복소수complex floating-point 타입
C99는 내장된 복소수 및 허수 타입을 제공하며, 각각 키워드
_Complex
, _Imaginary
를 써서 선언합니다.
C99에서 제공하는 복소수 및 허수 타입은 다음과 같습니다:
_Complex float
_Complex double
_Complex long double
_Imaginary long double
_Imaginary double
_Imaginary long double
C99는 <complex.h>
헤더를 제공하며, 이 안에는 복소수 타입 정의,
관련 매크로, 상수 등이 들어 있습니다. 특히, 이 헤더는 아래와 같은
매크로를 제공합니다:
complex |
_Complex 와 동일 |
imaginary |
_Imaginary 와 동일 |
I |
i (허수 단위) |
C 코드에서 이 헤더를 포함하지 않을 경우, 위 단어들을 다른 목적의
이름identifier이나 매크로 이름으로 쓸 수 있습니다. 사실
_Complex
나 _Imaginary
처럼 이상한 이름을 키워드를 만든 것도,
C89나 기존 C 코드에서, complex
나 imaginary
란 이름을 썼을 경우,
문제없이 동작할 수 있도록 하기 위해서 입니다.
(복소수가 아닌) 일반 실수 연산에서 함축적으로 확장되는 타입 변환implicit widening conversion이 이루어지는 것처럼, 복소수 및 허수 타입도 확장되는 변환이 이루어집니다.
// C99 code
#include <complex.h>
complex double square_d(complex double a)
{
return (a * a);
}
complex float square_f(complex float a)
{
complex double d = a; //
return square_d(a); //
}
C++은 <complex>
헤더를 통해, complex
라는 template class를 제공하며
이 타입은 C99의 complex
와 호환되지 않습니다.
이론적으로, C++의 complex
는 template class이기 때문에, C99보다 좀
더 많은 복소수 타입을 제공할 수 있습니다.
// C++ code
#include <complex>
complex<float> square(complex<float> a)
{
return (a * a);
}
complex<int> square(complex<int> a)
{
return (a * a);
}
약간의 제한을 감수할 수 있다면, C99와 C++ 모두에서 쓸 수 있는 typedef
을
만들 수 있습니다:
#ifdef __cplusplus
#include <complex>
typedef complex<float> complex_float;
typedef complex<double> complex_double;
typedef complex<long double> complex_long_double;
#else
#include <complex.h>
typedef complex float complex_float;
typedef complex double complex_double;
typedef complex long double complex_long_double;
typedef imaginary float imaginary_float;
typedef imaginary double imaginary_double;
typedef imaginary long double imaginary_long_double;
#endif
위 정의를 추가하면, C와 C++ 모두에서 쓸 수 있는 코드를 만들 수 있습니다. 예를 들면 다음과 같습니다:
complex_double square_cd(complex_double a)
{
return (a * a);
}
[C99: §6.2.5, 6.3.1.6, 6.3.1.7, 6.3.1.8] [C++98: §26.2]
복합 상수compound literal
C99에서는 단순primitive 타입이 아닌 (예: 사용자가 만든 구조체나 배열) 경우에도 상수constant expression로 쓸 수 있습니다. 이를 compound literal이라고 부릅니다. 예를 들면 다음과 같습니다:
struct info
{
char name[8+1];
int type;
};
extern void add(struct info s);
extern void move(float coord[2]);
void predef(void)
{
add((struct info){ "e", 0 }); // struct
move((float[2]){ +0.5, -2.7 }); //
}
C++은 이러한 기능을 지원하지 않습니다.
대신 C++에서는, default class constructor가 아닌 constructor를 통해 비슷한 기능을 쓸 수 있지만, C 만큼 자유롭지flexible 않습니다:
void predef2()
{
add(info("e", 0)); // info::info() constructor
}
(이 C 언어 기능은 몇몇 C++ 컴파일러에서도 확장 기능으로 제공하긴 하지만, POD structure type이나 POD type의 배열에서만 쓸 수 있습니다.)
역자 주: POD structure는 'plain old data structure'의 줄임말입니다.
좀 더 자세히, constructor나 destructor, virtual member function이 없는
struct
, union
, enum
, class
등을 뜻합니다.
[C99: §6.5.2, 6.5.2.5] [C++98: §5.2.3, 8.5, 12.1, 12.2]
const linkage
C 언어에서, const
한정사qualifier가 붙은 변수는 변경할modifiable 수 없는
오브젝트를 선언합니다. 변경할 수 없다는 것을 빼놓으면, 일반 변수와
같습니다. 구체적으로, 파일 스코프를 가진 const
오브젝트가
static
으로 선언되지 않았다면, 이 이름은 external linkage 속성을 가지고,
다른 소스 모듈에서 볼 수 있습니다.
const int i = 1; // External linkage
extern const int j = 2; // 'extern'
static const int k = 3; // 'static'
C++ 언어에서, 파일 스코프를 가진 const 오브젝트는 internal linkage
속성을 가집니다. 즉, 이 이름은 다른 소스 파일에서 보이지
않습니다. 다른 소스 파일에서 이 이름을 쓰려면, extern
으로
선언되어야 합니다.
const int i = 1; // Internal linkage
extern const int j = 2; // 'extern'
static const int k = 3; // 'static'
따라서 상수를 정의할 때, 명백하게explicitlystatic
이나 extern
을
반드시 써 주는 것이 좋습니다.
[C99: §6.2.2, 6.7.3] [C++98: §7.1.5.1]
Designated initializers
C99는 구조체, union, 또는 배열을 선언할 때, (멤버 이름이나 배열 인덱스subscript로) 특정 멤버만 초기화할 수 있는 designated initializer를 지원합니다. 예를 들면 다음과 같습니다:
struct info
{
char name[8+1];
int sz;
int typ;
};
struct info arr[] =
{
[0] = { .sz = 20, .name = "abc" },
[9] = { .sz = -1, .name = "" }
};
초기값이 없는 멤버는 디폴트 값으로 초기화됩니다default-initialized.
C++은 이 기능을 제공하지 않습니다.
(몇몇 C++ 컴파일러는 이 기능을 확장 기능으로 제공할 수 있습니다. 하지만 이 경우에도 POD 구조체 타입이나 POD 타입의 배열에만 쓸 수 있을 것입니다. 비슷한 기능을, defaut class constructor가 아닌 class constructor로 흉내낼 수도 있습니다.)
[C99: §6.7.8] [C++98: §8.5.1, 12.1]
중복된 typedef
C 언어에서, 같은 스코프 안에서, 같은 이름의 typedef
가 두 번 이상
나올 수 없습니다.
C++에서는, typedef
또는 타입 이름이 C 언어와 다르게 취급됩니다.
그래서 같은 스코프 안에서, 같은 이름의 typedef
가 두 번 이상 나올
수 있습니다.
따라서, 아래 코드는 올바른 C++ 코드이지만, 틀린 C 코드입니다:
typedef int MyInt;
typedef int MyInt; // C++ ok, C
따라서, 만약 같은 코드가 C 언어와 C++ 에서 동시에 쓰인다면 (예:
여러 헤더 파일에서 공통으로 쓰이는 typedef
), 전처리기
지시어preprocessing directive를 써서, 한 번만 정의되도록
해 주어야 합니다. 예를 들면 다음과 같습니다:
//========================================
// one.h
#ifndef MYINT_T
#define MYINT_T
typedef int MyInt;
#endif
...
//========================================
// two.h
#ifndef MYINT_T
#define MYINT_T
typedef int MyInt;
#endif
...
이렇게 하면, C 코드에서도 에러없이 여러 헤더 파일을 포함할 수 있습니다:
// Include multiple headers that define typedef MyInt
#include "one.h"
#include "two.h"
MyInt my_counter = 0;
[C99: §6.7, 6.7.7] [C++98: §7.1.3]
동적 sizeof
평가evaluation
C99는 가변 길이 배열variable-length array(VLA)을 지원하기 때문에,
sizeof
연산자가 항상 컴파일 시간 상수compile-time constant를
리턴하지는 않습니다. VLA에 sizeof
연산자를 쓰면, 그 결과는
런타임에 결정됩니다. (물론 VLA이 아닌 경우에는 컴파일 타임에 결정됩니다.)
예를 들면, 다음과 같습니다:
size_t dsize(int sz)
{
float arr[sz]; // (VLA)
if (sz <= 0)
return sizeof(sz); // [evaluated]
else
return sizeof(arr); // [evaluated]
}
C++은 가변 길이 배열(VLA)을 지원하지 않습니다. 따라서 가변 길이
배열에 sizeof
연산자를 쓴 코드를 C++로 컴파일할 경우, 문제가
발생합니다.
[C99: §6.5.3.4, 6.7.5, 6.7.5.2] [C++98: §5.3, 5.3.3]
빈empty 파라미터 리스트
C 언어에서, 파라미터 리스트가 비어있는 함수와, 파라미터 리스트에
void
만 있는 함수는 서로 뜻이 다릅니다. 전자는
프로토타입prototype이 없는 함수로, 인자argument의 갯수가
정해지지 않았다는 뜻이며, 후자의 경우, 이 함수는 인자를 받지
않는다는 뜻입니다.
// C code
extern int foo(); // . (unspecified)
extern int bar(void); // .
void baz()
{
foo(0); // C ok, C++
foo(1, 2); // C ok, C++
bar(); // C, C++ ok
bar(1); // C, C++
}
이와 달리, C++에서는, 이 두 가지 경우를 구별하지 않으며, 모두 인자를 받지 않는 것으로 해석합니다.
// C++ code
extern int xyz();
extern int xyz(void); // 'xyz()'
// C , C .
같은 코드가 C와 C++에 동시에 쓰인다면, 인자를 받지 않는 함수를
선언할 때, 반드시 void
를 써 준 프로토타입으로 선언하면 됩니다.
예를 들면, 다음과 같습니다:
// Compiles as both C and C++
int bosho(void)
{
...
}
참고로, C99에서 프로토타입이 없는 함수는 쓰는 것은 좋지 않습니다deprecated. (C89에서도 마찬가지임)
[C99: §6.7.5.3] [C++98: §8.3.5, C.1.6.8.3.5]
매크로 함수 인자 생략
C99에서는, 매크로 함수를 부를 때, 인자를 생략할 수 있습니다.
#define ADD3(a,b,c) (+ a + b + c + 0)
ADD3(1, 2, 3) => (+ 1 + 2 + 3 + 0)
ADD3(1, 2, ) => (+ 1 + 2 + + 0)
ADD3(1, , 3) => (+ 1 + + 3 + 0)
ADD3(1,,) => (+ 1 + + + 0)
ADD3(,,) => (+ + + + 0)
C++에서는, 인자를 생략할 수 없습니다.
(특정 C++ 컴파일러들은 이를 확장 기능으로 제공할 수도 있습니다.)
[C99: §6.10.3, 6.10.3.1] [C++98: §16.3., 16.3.1]
Enum 상수enumeration constant
C 언어에서 enum 상수enumeration constant는 본질적으로 이름을
붙인 signed int
타입입니다. 따라서 enum 상수의 초기값의 범위는
[ INT_MIN
, INT_MAX
]입니다. 따라서 enum 상수 RED
는 어떤
값을 가지든, sizeof(RED)
와 sizeof(int)
는 같습니다.
C++에서 enum 상수의 타입enumeration constant type은 해당
enum 타입enumeration type과 같습니다. 즉, 해당 내부 정수
타입underlying integer type과 같은 크기와 alignment를 갖습니다.
C++에서 enum 상수에 쓰이는 내부 정수 타입은 C 언어와 달리
여러 가지가 쓰일 수 있습니다: signed int
, unsigned int
, signed long
,
또는 unsigned long
. 따라서 enum 상수 초기값의 범위도 더 다양합니다.
즉, RED
가 enum 상수일 때, 항상 sizeof(RED)
와 sizeof(int)
가
같다고 보장할 수 없습니다.
그래서, C 코드가 C++로 컴파일된 경우, 만약, C++ 컴파일러가
내부적으로 C와 다른 내부 정수 타입을 쓴다면, 또는 sizeof(RED)
에
의존하는 코드를 쓴다면, 문제가 될 수 있습니다.
enum ControlBits
{
CB_LOAD = 0x0001,
CB_STORE = 0x0002,
...
CB_TRACE = LONG_MAX+1, // (Undefined behavior)
CB_ALL = ULONG_MAX
};
[C99: §6.4.4.3, 6.7.2.2] [C++98: §4.5, 7.2]
Enum 선언과 마지막 콤마comma
C99에서는, 구조체 초기값을 쓸 때와 비슷하게, 마지막 enum 상수 초기값enumeration constant initializer 다음에 콤마(,)가 나올 수 있습니다. 예를 들면, 다음과 같습니다:
enum Color { RED = 0, GREEN, BLUE, };
C++에서는 이를 허용하지 않습니다.
(특정 C++ 컴파일러는 이를 확장 기능 형태로 제공할 수도 있습니다.)
[C99: §6.7.2.2] [C++98: §7.2]
Enum 타입
C 언어에서, enum 타입enumerated type들이 서로 다른,
유일한unique 타입이며, 한 프로그램 안에서 다른 enum
타입들과 같지 않습니다. 따라서 C 컴파일러는 enum 타입에 대해
서로 다른, 내부 단순 정수 타입underlying primitive integer type을
쓸 수 있습니다. 이 말은, sizeof(enum A)
와 sizeof(enum B)
가 서로
다를 수 있다는 것을 뜻합니다. 또 RED
가 enum Color
의 enum 상수일 때,
sizeof(RED)
와 sizeof(enum Color)
가 서로 다를 수 있습니다.
(왜냐하면 모든 enum 상수의 타입은 signed int
이기 때문입니다.)
모든 enum 상수enumeration constant는 수식expression에 나타날
때, signed int
타입의 값으로 변환됩니다. enum 상수 값은
int
보다 클 수 없으므로, 자연스럽게 int
가 가장 넓은 범위의 enum
타입enumeration type이라고 생각할 수 있습니다. 하지만 C
컴파일러가 enum 타입에 어떤 정수 타입을 쓰는지는 컴파일러 마음대로
(일반적으로 int
와 같거나 더 큰 정수 타입) 결정할 수 있습니다.
만약 enum 타입에 이러한 확장 정수 타입을 쓴다면, 이는 C++에서
쓰이는 타입과 다를 수 있습니다.
C 언어에서는, 따로 명백한 캐스트explicit cast 없이, 정수값integer value을 enum 타입enumeration type의 오브젝트에 바로 대입할 수 있습니다. 예를 들면 다음과 같습니다:
// C code
enum Color { RED, BLUE, GREEN };
int c = RED; //
enum Color col = 1; //
C++에서도, 모든 enum 타입enumerated type은 유일하며unique,
각각 서로 다른 타입이며, 좀 더 나아가 더 강한 규칙을 가집니다.
특히, 서로 다른 enum 타입을 인자로 받는 오버로드overload된
함수를 만들 수 있습니다. Enum 타입의 오브젝트들은 정수형
타입으로 함축적으로 변환implictly converted될 수 있지만, 정수형
타입은 명백한 변환explicitly convert을 통해서만 enum 타입으로
변환될 수 있습니다. 함축적으로 변환된implcitly converted enum
값enumeration value은 해당하는 내부 정수
타입underlying integer type으로 변환되며, 반드시
signed int
일 필요는 없습니다.
예를 들면 다음과 같습니다:
// C++ code
enum Color { ... };
enum Color setColor(int h)
{
enum Color c;
c = h; // : [no implicit conversion]
return c;
}
int hue(enum Color c)
{
return (c + 128); // [Implicit conversion],
// signed int
}
C++에서는, enum 상수enumeration constant와 해당 enum
타입enumeration type은 서로 같은 타입과 크기를 갖습니다. 따라서
RED
가 enum Color
타입인 경우에 sizeof(RED)
와
sizeof(enum Color)
는 같습니다. (C 언어에서는 다를 수 있습니다.)
같은 enum 타입이 C와 C++에서, 모두 같은 내부 타입underlying type을
가진다는 보장은 없습니다. 또 서로 다른 C 컴파일러들에서 같은 내부
타입을 가진다는 보장도 없습니다. 이는 C와 C++ 사이의 호출
interface에 영향을 미치게 되며, 이것으로 인해, C 코드를 C++로
컴파일했을 경우, (C++ 컴파일러가 내부적으로 enum
타입enumeration type을 C 컴파일러와 다른 크기 타입으로 쓰거나,
sizeof(RED)
에
의존하는 수식expression을 쓴 경우) 호환성이 없을 수도 있습니다.
// C++ code
enum Color { ... };
extern "C" void foo(Color c);
// Parameter type
void bar(Color c)
{
foo(c); // Enum type
}
[C99: §6.4.4.3, 6.7.2.2] [C++98: §4.5, 7.2]
가변 배열 멤버flexible array members (FAMs)
이 기능은 'struct hack'이라고 알려져 있으며, 고정된 크기의
멤버들을 포함하고, 마지막에 가변 크기를 가지는 배열 멤버를 가질 수 있는
구조체를 선언하는 적합한conforming 방법을 제공합니다.
일반적으로 이런 구조체는 malloc()
을 통해 공간을 할당받으며,
할당하는 크기는 구조체의 크기에 필요한 공간을 더한 값을 사용합니다.
예를 들면 다음과 같습니다:
struct Hack
{
int count; // Fixed member(s)
int fam[]; // Flexible array member
};
struct Hack * vmake(int sz)
{
struct Hack * p;
p = malloc(sizeof(struct Hack) + sz*sizeof(int));
//
p->count = sz;
for (int i = 0; i < sz; i++)
p->fam[i] = i;
return p;
}
C++은 이런 기능flexible array member을 제공하지 않습니다.
(C++ 컴파일러가 확장 기능 형태로 이 기능을 제공할 수 있지만, POD 구조체 타입으로만 제한될 것입니다.)
[C99: §6.7.2.1] [C++98: §8.3.4]
함수 이름 mangling
오버로드overload된 함수와 멤버 함수를 지원하기 위해, C++
컴파일러는, 함수 이름을, 생성된 오브젝트 코드의 심볼symbol
이름으로 매핑mapping할 방법이 필요합니다. 예를 들어 함수
::foo(int)
, ::foo(float)
, 그리고 Mine::foo()
은 같은
이름(foo
)을 쓰지만, 부르는 방식calling signature이 달라야
합니다. 이런 함수들은, 링크 단계에서 이러한 함수 이름을 구별하기
위해, 서로 다른 심볼 이름을 써야 합니다.
역자 주: 함수 이름을 서로 다른 심볼 이름으로 변환하는 것을 'mangle'한다고 (또는 mangling한다고) 표현합니다.
C 언어에서는 (함수 이름이 심볼 이름으로 매핑되는) 이러한 과정이
C++과는 다릅니다. C 언어에서는 매핑 과정을 통해 signed
,
unsigned
구별을 안 하게 할 수도 있고, 프로토타입이 없는 extern
함수도 쓸 수 있게 해 줍니다. 어쨌든, C++로 컴파일할 C 코드는 서로
다른 심볼 이름을 갖게 되며, 같은 심볼 이름을 쓰게 할려면
extern "C"
로 선언되어야 합니다. 예를 들면, 다음과 같습니다:
int foo(int i); // Different symbolic names in C and C++
#ifdef __cplusplus
extern "C"
#endif
int bar(int i); // Same symbolic name in both C and C++
C++ 함수들은 함축적으로implicitlyextern "C++"
linkage 속성을
가집니다.
이러한 C++ 함수 이름 mangling에 의해, C++ 이름identifier들은 두
개 이상의 밑줄 문자를 가지는 것을 허용하지 않습니다. (예:
foo__bar
는 잘못된 C++ 이름입니다.) 이러한 이름name은
컴파일러implementation가 내부적으로 쓰도록 예약reserved되어
있습니다. 컴파일러는 이런 이름들을 써서,
유일한unique 심볼 이름을 만들어 냅니다. (예를 들어,
Mine::foo(int)
란 이름을 foo__4Mine_Fi
란 심볼 이름으로
매핑합니다)
C 언어는 이런 이름을 따로 예약reserved하지 않기 때문에, C 프로그램에서 이런 이름을 쓰는 것은 상관없습니다. 예를 들면, 다음과 같습니다.
void foo__bar(int i) // C++ .
{ ... }
[C99: §5.2.4.1, 6.2.2, 6.4.2.1] [C++98: §2.10, 3.5, 17.4.2.2, 17.4.3.1.2, 17.4.3.1.3]
함수 포인터
C++ 함수는 따로 지정하지 않았다면, extern "C++"
linkage 속성을
가집니다. C++에서 어떤 C 함수를 부르고 싶다면, 이 함수는
extern "C"
linkage 속성을 가져야 합니다.
일반적으로, 이는 extern "C"
블럭 안에,
C 함수들을 선언해서 해결합니다:
extern "C"
{
extern int api_read(int f, char *b);
extern int api_write(int f, const char *b);
}
extern "C"
{
#include "api.h"
}
그러나, 단순히 extern "C"
linkage 속성을 쓴다고 해서, 항상 C++ 함수가
C 함수를 부를 수 있는 것은 아닙니다.
좀 더 자세히 말하면, extern "C"
함수를 가리키는 포인터와 extern "C++"
함수를 가리키는 포인터는 호환되지 않습니다. C++ 코드로 컴파일 될 때,
함수 포인터는 함축적으로implicitlyextern "C++"
속성을 가진 것으로
간주되며, 따라서 extern "C"
함수의 주소를 대입할 수 없습니다.
(그래서 C API 라이브러리와 콜백callback 함수가 항상 문제가 되곤 합니다.)
extern int mish(int i); // extern "C++" function
extern "C" int mash(int i);
void foo(int a)
{
int (*pf)(int i); // C++
pf = &mish; // Ok, C++
(*pf)(a);
pf = &mash; // , C
(*pf)(a);
}
C++에서 함수 포인터와 extern "C"
함수가 올바르게 동작하게 만들려면,
C 함수 주소를 대입할 함수 포인터는 extern "C"
로 만들어야 합니다.
한가지 방법은, 올바른 linkage를 갖도록 아래와 같이 typedef
를 써서
해결할 수 있습니다:
extern "C"
{
typedef int (*Pcf)(int); // C
}
void bar(int a)
{
int (*pf)(int i); // C++
pf = &mish; // Ok, C++
(*pf)(a);
Pcf pc; // C
pc = &mash; // Ok, C
(*pc)(a);
}
[C99: §6.2.5, 6.3.2.3, 6.5.2.2] [C++98: §5.2.2, 17.4.2.2, 17.4.3.1.3]
16진 실수 상수
C99는 16진수로 표시한 실수 상수를 지원합니다. 상수 앞쪽에 "0x
"를 붙이고,
지수exponent 부분에 "p
"를 쓰면 됩니다. 예를 들면, 다음과 같습니다:
float pi = 0x3.243F6A88p+03;
또, C99는 printf()
와 scanf()
계열 함수에 새로운 형
지정자format specifier를 제공합니다:
printf("%9.3a", f);
printf("%12.6lA", d);
(C++ 컴파일러에서 이 기능을 추가 기능 형태로 제공할 수도 있습니다.)
[C99: §6.4.4.2, 6.4.8] [C++98: §2.9, 2.13.3]
IEC 60559 연산arithmetic 지원
어떤 C99 컴파일러implementation는 __STD_IEC_559
매크로를 미리
정의해 놓습니다. 이 경우, IEC 60559 (IEEE 599로도 알려져 있음)을
준수하는 형태로 실수floating-point 계산이 이루어집니다. 이
매크로를 정의하지 않은 컴파일러는 IEC 60559를 준수할 필요가
없습니다.
C++은 IEC 60559 실수 명세specification을 준수하는지 여부를 알려주지 않습니다.
그러나, C++ 컴파일러들은 이 기능(IEC 60559를 준수하는지, 또
__STD_IEC_559
매크로를 정의하는지)을 확장 기능 형태로 제공할 수도
있습니다.
또, C99는 컴파일러가 __STD_IEC_559_COMPLEX
매크로를 정의할 경우,
모든 복소수complex floating-point 연산이 IEC 60559에 정의되어 있는
방식으로 이루어질 것을 요구합니다.
이는 내부적으로 _Complex
와 _Imaginary
타입이 구현되어 있는 방식에
영향을 줍니다.
C++은 복소수 연산을 위해 complex<>
template class와 라이브러리
함수를 <complex>
헤더를 통해 제공하며, 이는 C99 복소수 타입과
호환되지 않습니다.
C++ 컴파일러는 복소수 계산 방식과 __STD_IEC_559
매크로를
확장 기능으로 제공할 수 있으며, 이는 complex<>
template class가
구현되는 방식에 영향을 줍니다.
[C99: §6.10.8, F, G] [C++98: §16.8]
Inline 함수
C99와 C++ 모두, inline 함수를 제공합니다. 이는 컴파일러가 해당 함수가 일반 함수 호출이 아닌, 함수 내용이 바로 실행되는 형태inline code expansion로 할 수 있는 힌트를 제공합니다. 현실적으로 inline 함수가 C99와 C++ 사이에 호환성 문제를 일으킬 가능성은 없지만, 약간의 차이는 존재합니다.
C++ 언어에서는, 같은 inline 함수의 정의가 여러 번 나올 경우, 토큰 단위로 완벽하게 같아야same token sequence 합니다.
이와는 달리, C99에서는, 한 inline 함수의 정의가 여러번 나올 경우, 서로 달라도 상관없습니다. 특히, 컴파일러가 이 차이를 미리 감지할 필요도 없고, 경고diagnostic를 출력할 필요도 없습니다.
아래 두 소스 파일은, 같은 inline 함수에 대해, 약간 다른 정의를 가지고 있으며, 따라서 C99에서는 올바른 코드지만, C++에서는 틀린 코드입니다:
//========================================
// one.c
inline int twice(int i) //
{
return i * i;
}
int foo(int j)
{
return twice(j);
}
//========================================
// two.c
typedef int integer;
inline integer twice(integer a) //
{
return (a * a);
}
int bar(int b)
{
return twice(b);
}
현실적으로, 이것이 문제가 될 소지는 별로 없습니다. 일반적으로, inline 함수 정의는 공통으로 쓰는 헤더 파일에 있으므로, 토큰 단위로 같기same token sequences 때문입니다.
[C99: §6.7.4] [C++98: §7.1.2]
정수 타입 헤더 파일
C99는 헤더 <stdint.h>
를 통해 표준 정수 타입에 대한 선언과 매크로 정의를
제공합니다. 예를 들면, 다음과 같습니다:
int height(int_least32_t x);
int width(uint16_t x);
C++은 이러한 타입이나 헤더를 제공하지 않습니다.
(확장 기능으로 이를 제공하는 C++ 컴파일러가 있을 수 있으며, 어떤
C++ 컴파일러들은 <cstdint>
헤더를 제공하기도 합니다.)
[C99: §7.1.2, 7.18] [C++98: §17.4.1.2, D.5]
라이브러리 함수 프로토타입prototype
C++ 표준 라이브러리 헤더 파일은 (C++에서 좀 더 강한 타입 검사를 쓸 수 있도록) 몇몇 표준 C 라이브러리 함수 선언을 고쳐서 제공합니다. 예를 들어, 아래 표준 C 라이브러리 함수 선언은:
// <string.h>
extern char * strchr(const char *s, int c);
C++ 라이브러리에서 아래처럼 바뀝니다:
// <cstring>
extern const char * strchr(const char *s, int c);
extern char * strchr(char *s, int c);
이런 차이는 C 코드를 C++에서 컴파일할 때 문제가 될 수 있습니다. 예를 들면:
// C code
const char * s = ...;
char * p;
p = strchr(s, 'a'); // C ok, C++
즉, 함수에서 리턴된 const
포인터를 const
가 아닌 변수에
대입하려고 했기 때문에, 문제가 발생하는 것입니다. 아래와 같이,
간단한 캐스트cast를 써서, C++과 C 모두에서 동작하게 할 수
있습니다:
// C++
p = (char *) strchr(s, 'a'); // C C++ ok
[C99: §7.21.5, 7.24.4.5] [C++98: §17.4.1.2, 21.4]
라이브러리 헤더 파일
C++ 표준 라이브러리는 C89 표준 라이브러리를 포함하고 있습니다. (아래는 예외:)
<complex.h> |
<fenv.h> |
<inttypes.h> |
<stdbool.h> |
<stdint.h> |
<tgmath.h> |
C++이 C89 표준 라이브러리를 포함하고 있지만, 이를 사용하는 것은 권장하지 않습니다deprecated. 대신에 같은 기능을 하는 C++ 헤더가 따로 제공됩니다:
<math.h> |
대신에 | <cmath> |
<stddef.h> |
대신에 | <cstddef> |
<stdio.h> |
대신에 | <cstdio> |
<stdlib.h> |
대신에 | <cstdlib> |
… | … |
따라서, C++에서 다음과 같이 deprecated된 헤더를 쓰게 되면, 미래에 나올 C++ (표준) 컴파일러로 컴파일되지 않을 수도 있습니다:
#include <stdio.h> // C++ deprecate
int main(void)
{
printf("Hello, world\n");
return 0;
}
미래 버전 C++에서도 잘 돌아가게 하려면, 아래처럼 고치면 됩니다:
#ifdef __cplusplus
#include <cstdio> // C++ only
using std::printf;
#else
#include <stdio.h> // C only
#endif
int main(void)
{
printf("Hello, world\n");
return 0;
}
[C99: §7.1.2] [C++98: §17.4.1.2, D.5]
long long
정수 타입
C99는 signed long long
과 unsigned long long
정수 타입을 추가로 제공하며,
이들은 적어도 64 비트 이상입니다.
또, C99는 이들 정수 타입의 상수를 만들기 위한 어휘 규칙lexical rule도 가지고 있습니다. 예를 들면, 아래와 같습니다:
long long int i = -9000000000000000000LL;
unsigned long long int u = 18000000000000000000LLU;
또, C99는 새 정수 타입을 위한 매크로를 <limits.h>
에 추가했고,
printf()
와 scanf()
계열 함수에, 새 형 지정자format specifier를
제공하며, 이 타입을 위한 추가 함수도 제공합니다. 예를
들면 다음과 같습니다:
void pr(long long i)
{
printf("%lld", i);
}
C++은 이 정수 타입을 제공하지 않습니다.
(아마도, C 언어용 라이브러리도 제공하는 C++ 컴파일러 환경에서는 새 정수 타입을, 추가 기능 형태로 제공할 것입니다.)
[C99: §5.2.4.2.1, 6.2.5, 6.3.1.1, 6.4.4.1, 6.7.2, 7.12.9, 7.18.4, 7.19.6.1, 7.19.6.2, 7.20.1, 7.20.6, 7.24.2.1, 7.24.2.2, 7.24.4, A.1.5, B.11, B.19, B.23, F.3, H.2] [C++98: §2.13.1, 3.9.1, 21.4, 22.2.2.2.2, 27.8.2, C.2]
중첩된 구조체 태그structure tag
두 언어 모두, 한 구조체 안에서 다른 구조체를 선언할 수 있습니다.
C 언어에서 내부에 중첩되게nested 선언된 구조체 태그structure tag의
스코프는 바깥 구조체와 같습니다. 단, C++에서는
다릅니다. C++에서 내부 구조체는 (C 언어와 달리) 그 자신만의
스코프를 따로 가집니다. 이 규칙은 struct
, union
, enum
타입
모두 적용됩니다. 예를 들면, 다음과 같습니다:
struct Outer
{
struct Inner //
{
int a;
float f;
} in;
enum E // enum
{
UKNOWN, OFF, ON
} state;
};
struct Inner si; // C ok: .
// C++ .
enum E et; // C ok: .
// C++ .
C++에서 바깥 class prefix를 명백하게explicitly 써 주면, C++에서도 중첩된 내부 선언을 (바깥 쪽에서) 쓸 수 있습니다. 또는, 중첩해서 선언하지 말고, 파일 스코프를 갖도록, 맨 바깥쪽에 별도로 선언하면 문제가 해결됩니다. 전자의 경우 예는, 아래와 같습니다:
// C++
Outer::Inner si; // Explicit type name
Outer::E et; // Explicit type name
후자의 예는 아래와 같습니다:
// C, C++
struct Inner //
{
int a;
float f;
};
enum E //
{
UKNOWN, OFF, ON
};
struct Outer
{
struct Inner in;
enum E state;
};
[C99: §6.2.1, 6.2.3, 6.7.2.1, 6.7.2.3] [C++98: §9.9, C.1.2.3.3]
프로토타입이 아닌non-prototype 함수 선언
C 언어에서는, (K&R 스타일이라고 알려진) 프로토타입prototype이 아닌, 함수 정의를 할 수 있습니다. (그러나, C90, C99 모두, 이 방식을 권장하지 않습니다deprecated practice) 예를 들면, 아래와 같습니다:
int foo(a, b) // Deprecated syntax
int a;
int b;
{
return (a + b);
}
C++ 언어는 프로토타입 형태의 함수 정의만 지원합니다. 따라서, 위 코드를 C++에서 쓰려면, 아래와 같이 프로토타입 형태로 바꿔야 합니다:
int foo(int a, int b)
{
return (a + b);
}
[C99: §6.2.7, 6.5.2.2, 6.7.5.3, 6.9.1, 6.11.6, 6.11.7] [C++98: §5.2.2, 8.3.5, 8.4, C.1.6]
오래된 스타일 캐스트cast
C++은 아래와 같은 형태의 형변환typecast 연산자를 제공합니다:
const_cast |
dynamic_cast |
reinterpret_cast |
static_cast |
아래 C 코드는 올바른 C++98 코드이지만, 미래의 C++ 표준에서는 틀린 코드가 될 가능성이 높습니다.
char * p;
const char * s = (const char *) p;
한가지 방법은, C++ 형변환typecast 연산자를 흉내내는 매크로를 만들어서 쓰는 것입니다:
#ifdef __cplusplus
#define const_cast(t,e) const_cast<t>(e)
#define dynamic_cast(t,e) dynamic_cast<t>(e)
#define reinterpret_cast(t,e) reinterpret_cast<t>(e)
#define static_cast(t,e) static_cast<t>(e)
#else
#define const_cast(t,e) ((t)(e))
#define dynamic_cast(t,e) ((t)(e))
#define reinterpret_cast(t,e) ((t)(e))
#define static_cast(t,e) ((t)(e))
#endif
const char * s = const_cast(const char *, p);
위에는 포함시켰습니다만, dynamic_cast
는 사실상 C 언어에서 의미가 없습니다.
차라리 아래처럼 만드는 것이 더 나을 수도 있습니다:
#define dynamic_cast(t,e) _Do_not_use_dynamic_cast
//
C++은 함수 형태의 형 변환functional typecast도 제공하며, 이는 C 언어에서 쓸 수 없습니다:
f = float(i); // C++: float , C
C와 C++ 용으로 동시에 쓰일 코드에는 위와 같은 형변환typecast을 쓰면 안됩니다.
[C99: §6.3, 6.54] [C++98: §5.2, 5.2.3, 5.2.7, 5.2.9, 5.2.10, 5.2.11, 5.4, 14.6.2.2, 14.6.2.3]
단일 정의 규칙one definition rule
C 언어는 변수를 정의할 때, 여러번 정의할 수 있는데, 이 때 초기값이 없는 정의를 잠정적tentative 정의라고 합니다.
역자 주: 아무 변수나 잠정적 정의를 쓸 수 있는 것은 아닙니다.
파일 스코프 변수이어야 하고, storage class specifier가 없거나,
static
이어야 합니다.
int i; // [tentative definition]
int i = 1; // [explicit definition]
C++은 이를 허용하지 않습니다. 반드시 한 변수의 정의는 한번만 나와야 합니다.
C 언어는 다른 소스 파일에서 다른 정의를 제공하는 것을 허용하며, 이것을 위한 경고 등을 제공할 의무도 없습니다. 예를 들면 아래와 같습니다:
//========================================
// one.c
struct Capri // A declaration
{
int a;
int b;
};
//========================================
// two.c
struct Capri // Conflicting declaration
{
float x;
};
C++에서는, 위 코드는 틀린 코드입니다. C++은 두 정의가 토큰 단위로 같아야same sequence of tokens 합니다.
C 언어는 같은 함수나 오브젝트의 정의가 다른 소스 파일에 다른 토큰들로 정의되는 것을 허용합니다. 물론 다른 토큰들로 정의되는 것을 허용하지만, 의미상 같은 정의여야만 합니다.
C++ 규칙은 좀 더 엄격하기 때문에, 정의가 여러 번 나용 경우, 토큰 단위로 같아야 합니다. 아래 코드는 의미상 같지만, 문법적으로 (토큰 단위로) 다르기 때문에, 올바른 C 코드이지만, 틀린 C++ 코드입니다:
//========================================
// file1.c
struct Waffle //
{
int a;
};
int syrup(int amt) //
{
return amt*2;
}
//========================================
// file2.c - Valid C, but invalid C++
typedef int IType;
struct Waffle //
{ //
IType a;
};
IType syrup(IType quant) //
{ //
return (quant*2);
}
[C99: §6.9.2, J.2] [C++98: §3.2, C.1.2.3.1]
_Pragma
키워드
C99는 _Pragma
키워드를 제공합니다. 이는 #pragma
전처리기
지시어preprocessor directive와
같은 역할을 합니다. 예를 들어 아래 두 문장은 같습니다:
#pragma FLT_ROUND_INF // pragma
_Pragma(FLT_ROUND_INF) // Pragma [statement]
C++은 _Pragma
키워드를 지원하지 않습니다.
(몇몇 C++ 컴파일러는 이를 확장 기능으로 제공할 수 있습니다.)
[C99: §5.1.1.2, 6.10.6, 6.10.9] [C++98: §16.6]
-
역자 주:
_Pragma
의 목적C90에서는, 매크로 확장 결과로
#pragma
를 쓸 수 없습니다._Pragma
키워드는 매크로 확장 결과로 쓰일 수 있습니다. 예를 들어, armcc 컴파일러를 쓸 경우, 특정 함수나 변수를 오브젝트 파일의 지정된 섹션section에 넣기 위해, 아래와 같이 할 수 있습니다:# define RWDATA(X) PRAGMA(arm section rwdata=#X) # define PRAGMA(X) _Pragma(#X) RWDATA(foo) // same as #pragma arm section rwdata="foo" int y = 1; // y is placed in section "foo"
미리 정의된 이름identifier
C99는 미리 정의된 이름predefined identifier으로, __func__
를 제공하며,
이는 항상 해당 함수의 이름을 가리키는 문자열 상수string literal입니다.
예를 들면 다음과 같습니다:
int incr(int a)
{
fprintf(dbgf, "%s(%d)\n", __func__, a);
return ++a;
}
(몇몇 C++ 컴파일러는 이를 확장 기능 형태로 제공하지만, 어떤 값을 나타내는지 확실하지 않습니다. 특히 중첩된nested namespace 안의 중첩된nested template class의 멤버 함수에 대해 그렇습니다.)
[C99: §6.4.2.2, 7.2.1.1, J.2]
C99 예약된reserved 키워드
아래 키워드들은 C++이 인식할 수 없는, C99 키워드입니다:
restrict |
_Bool |
_Complex |
_Imaginary |
_Pragma |
따라서, 이 키워드를 쓴 C 코드를 C++로 컴파일할 경우 문제가 됩니다:
extern int set_name(char *restrict n);
[C99: §6.4.1, 6.7.2, 6.7.3, 6.7.3.1, 6.10.9, 7.3.1, 7.16, A.1.2] [C++98: §2.11]
C++ 예약된reserved 키워드
아래 키워드들은 C99가 인식할 수 없는, C++ 키워드입니다:
bool |
mutable |
this |
catch |
namespace |
throw |
class |
new |
true |
const_cast |
operator |
try |
delete |
private |
typeid |
dynamic_cast |
protected |
typename |
explicit |
public |
using |
export |
reinterpret_cast |
virtual |
false |
static_cast |
wchar_t |
friend |
template |
또, C++은 asm
키워드를 예약reserve하고 있습니다. C의 경우,
컴파일러implementation에 따라 예약reserved되어 있을 수도 있고,
아닐 수도 있습니다.
C 코드에서는 위 C++ 키워드들을 마음대로 다른 이름identifier이나 매크로 이름으로 쓸 수 있습니다. 대신 이런 코드를 C++로 컴파일할 경우, 문제가 됩니다:
extern int try(int attempt);
extern void frob(struct template *t, bool delete);
[C99: §6.4.1] [C++98: §2.11]
restrict
키워드
C99는 restrict
키워드를 제공하며, 포인터에 대한 최적화를 수행할 수 있게
도와주는 역할을 합니다. 예를 들면:
void copy(int *restrict d, const int *restrict s, int n)
{
while (n-- > 0)
*d++ = *s++;
}
C++은 이 키워드를 인식하지 못합니다.
C와 C++ 모두에 쓰일 코드라면, 간단하게 restrict
키워드를 위한 매크로를
만들어서 해결할 수 있습니다.
#ifdef __cplusplus
#define restrict /* nothing */
#endif
(이 기능은 몇몇 C++ 컴파일러에서 확장 기능으로 제공할 수 있습니다. 또, 확장 기능을 제공하는 컴파일러라면, 이 키워드를 pointer 뿐만 아니라 reference에도 쓸 수 있게 해 줄 것입니다.)
[C99: §6.2.5, 6.4.1, 6.7.3, 6.7.3.1, 7, A.1.2, J.2] [C++98: §2.11]
void
리턴하기
C++은 리턴 타입이 void
인 함수에서 void
타입의 수식expression을
리턴하는 것을 허용합니다. C 언어 void
함수는, 어떤 수식expression도
리턴할 수 없습니다.
예를 들면:
void foo(someType expr)
{
...
return (void)expr; // C++ ok, C
}
C++에서 이것이 허용된 이유는, template 함수가 template 파라미터
용으로, (void
를 포함한) 어떤 return type이라도 받을 수 있게 하기
위해서입니다. 예를 들어:
// C++ code
template <typename T>
T bar(someType expr)
{
...
return (T)expr; // T void ok
}
[C99: §6.8.6.4] [C++98: §3.9.1, 6.6.3]
static
linkage
C와 C++ 모두에서, 오브젝트나 함수 모두 (internal linkage라고도
하는) static file linkage 속성을 가질 수 있습니다. 이와 달리,
C++에서는 이를 권장하지 않습니다deprecated. 대신, 이름이
없는unnamed namespace를 쓸 것을 권장합니다. 이름이 없는
namespace 안에 선언된 C++ 오브젝트나 함수는 (static
으로 선언되지
않은 한) external linkage 속성을 가집니다. C++에서 namespace
스코프 안에 선언된 오브젝트나 함수 선언에 static
을 쓰는 것은
권장하지 않습니다deprecated.)
C++98로 컴파일된 C 코드에서 문제될 것은 없지만, 미래 버전 C++ 표준에는
문제가 될 수 있습니다. 예를 들어 아래는 deprecate된 static
을 사용한
코드입니다:
// C and C++ code
static int bufsize = 1024;
static int counter = 0;
static long square(long x)
{
return (x * x);
}
아래는, 권장하는, C++ 방식으로 작성된 코드입니다:
// C++ code
namespace /* */
{
static int bufsize = 1024;
static int counter = 0;
static long square(long x)
{
return (x * x);
}
}
(static
지정자specifier를 쓰는 것은 불필요합니다.)
한 가지 방법으로, 전처리기preprocessor 매크로와 wrapper를 사용하여 해결할 수도 있습니다:
// C and C++ code
#ifdef __cplusplus
#define STATIC static
#endif
#ifdef __cplusplus
namespace /*unnamed*/
{
#endif
STATIC int bufsize = 1024;
STATIC int counter = 0;
STATIC long square(long x)
{
return (x * x);
}
#ifdef __cplusplus
}
#endif
[C99: §6.2.2, 6.2.4, 6.7.1, 6.9, 6.9.1, 6.9.2, 6.11.2] [C++98: §3.3.5, 3.5, 7.3.1, 7.3.1.1, D.2]
문자열 초기값
C 언어에서 문자 배열은 문자열 상수로 초기화할 수 있습니다. 이 때, 문자열 상수 길이는 배열의 크기와 같거나, 하나만큼 더 클 수 있습니다. 이는 문자열 끝을 나타내는 널null 문자를 위한 것입니다. 예를 들면:
char name1[] = "Harry"; //
char name2[6] = "Harry"; //
char name3[] = { 'H', 'a', 'r', 'r', 'y', '\0' };
// 'name1'
char name4[5] = "Harry"; // ,
C++에서도 문자 배열을 문자열 상수로 초기화 할 수 있습니다. 다만,
초기값의 널 문자도 반드시 배열에 들어가야 합니다.
따라서 위 예제의 마지막 선언(name4
)은 틀린 C++ 코드입니다.
[C99: §6.7.8] [C++98: §8.5, 8.5.2]
문자열 상수string literal와 const
C 언어에서 문자열 상수literal는 char[n]
타입이며, 변경할 수
없습니다. (즉, 문자열 상수literal의 내용을 바꾸는 것은 정의되어
있지 않습니다undefined behavior.)
C++에서 문자열 상수literal은 const char[n]
타입이며,
변경할 수 없습니다.
문자열 상수가 수식expression에 쓰일 때 (혹은 함수에 전달될 때),
C와 C++ 모두 문자열 상수를 char *
를 가리키는 포인터로
변환합니다. (특히, C++ 변환은 두 단계를 거칩니다. 먼저 const
char[n]
타입을 const char *
타입으로 변환하는 배열-포인터 변환을
수행하고, 그 다음으로 qualification 변환conversion을 거쳐
char *
타입으로 변환합니다.)
아래 코드는 C와 C++ 모두에서 올바른 코드입니다:
extern void frob(char *s);
// const char *
void foo(void)
{
frob("abc"); // C, C++ ok
// char *
}
따라서, 이 내용은 C99와 C++98 사이에 호환성 문제를 일으키지
않습니다. 그러나 C++에서 함축적 변환implicit converion은
deprecate된 기능입니다. (아마도 두번 변환 대신 const char *
타입의 단일single 함축적 변환implicit conversion으로 바뀔
것으로 예상됨.) 따라서 미래 버전 C++에서는 위 코드가 틀린 코드일 수
있습니다.
[C99: §6.3.2.1, 6.4.5, 6.5.1, 6.7.8] [C++98: §2.13.4, 4.2, D.4]
함수 프로토타입에서 구조체 선언
C 언어는 함수 프로토타입prototype 스코프 안에서 struct
,
union
, enum
타입을 정의하는 것을 허용합니다. 예를 들어:
extern void foo(const struct info { int typ; int sz; } *s);
int bar(struct point { int x, y; } pt)
{ ... }
또, C 언어는 함수 리턴 타입에 구조체를 선언하는 것을 허용합니다. 예를 들면:
extern struct pt { int x; } pos(void);
C++은 위 두 기능 모두 허용하지 않습니다. 왜냐하면, C++에서 이런 식으로 선언된 구조체의 스코프는 함수 선언 또는 정의 바깥으로 확장되지 않기 때문에, 해당 구조체의 오브젝트를 정의하는 것이 불가능하며, 정의가 불가능하기 때문에, 이 함수의 리턴 값을 대입할 변수나, 이 함수에 전달할 인자를 정의하는 것이 불가능해집니다.
반면 두 언어 모두, 함수 프로토타입 안에, 또는 함수 리턴 타입에 불완전한incomplete 구조체 타입을 쓰는 것은 지원합니다:
void frob(struct sym *s); // ok
struct typ * fraz(void); //
[C99: §6.2.1, 6.7.2.3, 6.7.5.3, I] [C++98: §3.3.1, 3.3.3, 8.3.5, C.1.6.8.3.5]
여러 타입에 쓸 수 있는 수학 함수
C99는 여러 타입에 쓸 수 있는type-generic 수학 함수를
제공합니다. 이 함수들은 근본적으로 세가지 실수 타입(float
,
double
, long double
)과 세가지 복소수 타입(complex float
,
complex double
, complex long double
)을 지원하는
오버로드된overloaded 함수입니다.
이 함수들을 쓰려면 <tgmath.h>
헤더를 써야 하며, 이 함수들은
매크로로 이루어져 있으며, 아마도 컴파일러에 따라 다른
이름implementation-defined name으로 치환될 것입니다.
예를 들어, 아래는 이런 함수들을 정의하는 방법 중 하나입니다:
/* Equivalent <tgmath.h> contents:
* extern float sin(float x);
* extern double sin(double x);
* extern long double sin(long double x);
* extern float complex sin(float complex x);
* extern double complex sin(double complex x);
* extern long double complex sin(long double complex x);
* ...
*/
// Macro
#define sin __tg_sin // [built-in]
#define cos __tg_cos // [built-in]
#define tan __tg_tan // [built-in]
...
C++도 마찬가지로 여러 타입에 쓸 수 있는 함수를 제공합니다. 사실 오버로드된 함수를 여러 개 제공하는 것은, C++이 가진 특징 중 하나입니다.
(여러 C++ 컴파일러implementation들은 이런 수학 함수들을 확장 기능 형태로 제공하지만, C99의 수학 함수들과 상당히 다를 수 있습니다. 특히 이런 여러 타입에 쓸 수 있는 함수들에 대한 포인터의 동작 방식은 두 언어가 다를 것입니다.)
[C99: §7.22] [C++98: §13, 13.1, 13.3.1, 13.3.2, 13.3.3]
typedefs
와 타입 태크type tags
C 언어에서는 struct
, union
, enum
키워드 다음에 타입
태그type tag가 나옵니다.
C++에서는 타입 태그를 내부적으로implicittypedef
이름으로 간주합니다.
따라서, 아래 코드는 올바른 C 코드이지만, 틀린 C++ 코드입니다.
// Valid C, invalid C++
typedef int type;
struct type
{
type memb; // int
struct type * next; // struct pointer
};
void foo(type t, int i)
{
int type;
struct type s;
type = i + t + sizeof(type);
s.memb = type;
}
typedef
를 처리하는 방식이 두 언어에서 다르기 때문에, C와 C++ 모두
올바른 코드라도, 각각 다른 방식으로 동작하는 코드가 나올 수 있습니다.
예를 들어:
int sz = 80;
int size(void)
{
struct sz
{ ... };
return sizeof(sz); // C sizeof(int)
// C++ sizeof(struct sz)
}
[C99: §6.2.1, 6.2.3, 6.7, 6.7.2.1, 6.7.2.2, 6.7.2.3] [C++98: §3.1, 3.3.1, 3.3.7, 3.4, 3.4.4, 3.9, 7.1.3, 7.1.5, 7.1.5.2, 9.1]
가변 인자 함수 선언
C90 문법으로, 함수 파라미터 리스트의 마지막에 생략
부호ellipsis(...
)를 쓸 수 있습니다. 이런 함수는, 마지막 함수
인자 자리에 0개 또는 그 이상의 추가 인자를 받을 수 있다는 것을
나타냅니다.
C++에서도 가변 함수 인자 리스트를 지원하지만, 두가지 방식syntactical form을 제공합니다.
/* Variable-argument function declarations */
int foo(int a, int b, ...); // C, C++ ok
int bar(int a, int b ...); // C++ ok, C
[C99: §6.7.5] [C++98: §8.3.5]
가변 인자 매크로 함수
C99는 가변 인자를 받을 수 있는 매크로 함수를 지원합니다. 매크로
정의 부분 파라미터 리스트 마지막에 '...
' 토큰을 쓰고, 매크로 치환
텍스트 자리에 __VA_ARGS__
를 써서 만들 수 있습니다.
예를 들면, 다음과 같습니다:
#define DEBUGF(f,...) \
(fprintf(dbgf, "%s(): ", f), fprintf(dbgf, __VA_ARGS__))
#define DEBUGL(...) \
fprintf(dbgf, __VA_ARGS__)
int incr(int *a)
{
DEBUGF("incr", "before: a=%d\n", *a);
(*a)++;
DEBUGL("after: a=%d\n", *a);
return (*a);
}
C++은 이 기능을 제공하지 않습니다.
(이 기능을 확장 기능 형태로 제공하는 C++ 컴파일러도 있습니다.)
[C99: §6.10.3, 6.10.3.1, 6.10.3.4, 6.10.3.5] [C++98: §16.3, 16.3.1]
가변 인자 배열variable-length array (VLAs)
C99는 가변 길이 배열variable-length array, 이하 VLA을 지원합니다. 이런 가변 길이 배열은 'automatic storage' 특성을 가지고, 배열의 크기는 프로그램 실행 시간program execution time에 동적으로 결정됩니다. 예를 들면:
size_t sum(int sz)
{
float arr[sz]; // VLA
while (sz-- > 0)
arr[sz] = sz;
return sizeof(arr); // [evaluate]
}
C99는 또, VLA 타입을 함수 파라미터로 쓸 수 있게, 새 선언 문법을
제공합니다. 즉, 기존에는 배열인 함수 파라미터의 대괄호 사이에
크기를 나타내는 정수 상수 수식constant integer size expression만
올 수 있었지만, (새 문법으로) '*
'를 쓰거나
변수 이름variable identifier을 쓸 수 있습니다.
아래는 함수에 전달되는 VLA에 관한 예제입니다:
extern float sum_square(int n, float a[*]);
float sum_cube(int n, float a[m])
{
...
}
void add_seq(int n)
{
float x[n]; // VLA
float s;
...
s = sum_square(n, x) + sum_cube(n, x);
...
}
VLA 함수 파라미터 선언에 쓴 '*
'는 (프로토타입 형태의) 함수
선언에만 쓸 수 있으며, 함수 정의에는 쓸 수 없습니다. 또 이 기능은
sizeof
수식expression에 영향을 주는 것도 알아두기 바랍니다.
C++은 VLA을 지원하지 않습니다.
[C99: §6.7.5, 6.7.5.2, 6.7.5.3, 6.7.6] [C++98: §8.3.4, 8.3.5, 8.4]
void
포인터 대입
C 언어에서는, void
를 가리키는 포인터 (void *
) 값을, 캐스트
필요없이, 아무런 타입의 포인터 타입에 대입할 수 있습니다. 따라서
명백한 캐스트explicit cast 필요없이 malloc()
이 리턴하는 값을,
아무 포인터 변수에나 대입할 수 있는 것입니다.
C++에서는, void
를 가리키는 포인터를, 명백한 캐스트explicit cast없이,
다른 포인터 타입에 대입할 수 없습니다. 이런 대입은
타입 안정type safety에 위배되기 때문에 명백한
캐스트explicit cast가 필요합니다. 따라서
아래 코드는 올바른 C 코드이지만, 틀린 C++ 코드입니다:
extern void * malloc(size_t n);
struct object * allocate(void)
{
struct object * p;
p = malloc(sizeof(struct object));
//
// C ok, C++
return p;
}
(두 언어 모두, 명백한 캐스트explicit cast없이 어떤 타입의 포인터
값이나 void
를 가리키는 포인터에 대입할 수 있습니다.
void * vp;
Type * tp;
vp = tp; // .
// C, C++ ok
이러한 쓰임새는 타입 안전type safe한 것으로 간주됩니다.)
(C++에서 아무런 포인터가 함축적으로implicitlyvoid
를 가리키는
포인터로 변환될 때도 있습니다. 예를 들면, void
를 가리키는
포인터를 가리키는 포인터를 다른 타입을 가리키는 포인터와 비교할
때가 그렇습니다. 하지만 이 과정에서 어떠한 포인터 오브젝트도
변경되지 않기 때문에 타입 안전type safe한 것으로 간주됩니다.)
[C99: §6.2.5, 6.3.2.3, 6.5.15, 6.5.16, 6.5.16.1] [C++98: §3.8, 3.9.2, 4.10, 5.4, 5.9, 5.10, 5.16, 5.17, 13.3.3.2]
와이드wide 문자 타입
C 언어는 와이드 문자wide character 타입인 wchar_t
를
제공합니다. 이 타입은 확장 문자 세트extended character set에
속한 와이드 문자wide character 한 개를 나타낼 수 있습니다.
이 타입은 표준 헤더 <stddef.h>
, <stdlib.h>
, <wchar.h>
에
정의되어 있습니다.
C++도 wchar_t
타입을 제공하지만, C++에서는 int
처럼
예약된reserved 키워드입니다. 따라서 wchar_t
타입을 쓰기 위해
어떤 헤더 파일을 포함할 필요는 없습니다.
따라서, 표준 헤더 파일을 포함하지 않은 C 코드의 경우, wchar_t
를
다른 이름identifier이나 매크로 이름으로 쓸 수 있습니다. 대신
이런 코드는 C++에서 컴파일되지 않습니다.
// <stddef.h>, <stddef.h> <wchar.h>
typedef unsigned short wchar_t;
wchar_t readwc(void)
{
...
}
따라서, wchar_t
를 다른 목적으로 쓰는 것은 권장되지 않습니다. 또
이 타입을 쓰기 전에 <stddef.h>
, <stdlib.h>
, 또는 <wchar.h>
를
포함하는 것이 좋습니다.
(대부분 C++ 컴파일러implementation는 <wchar.h>
를, 확장 기능
형태로 제공합니다. 어떤 컴파일러는 비어있는 <cwchar>
헤더를
제공하기도 합니다.)
[C99: §3.7.3, 6.4.4.4, 6.4.5, 7.1.2, 7.17, 7.19.1, 7.20, 7.24] [C++98: §2.11, 2.13.2, 2.13.4, 3.9.1, 4.5, 7.1.5.2]
참고 문헌
C89
Programming Languages - C ANSI/ISO 9899:1989, 1989, Available at http://www.ansi.org/.
C90
Programming Languages - C (with ISO amendments) ISO/IEC 9899:1990, 1990, ISO/IEC JTC1/SC22/WG14. Available at http://www.ansi.org/.
C99
Programming Languages - C * *ISO/IEC 9899:1999, 1999, ISO/IEC JTC1/SC22/WG14. Available at http://www.ansi.org/.
C++98
Programming Languages - C++ ISO/IEC 14882:1998(E), 1998-09-01, 1st ed., ISO/IEC JTC1/SC22. Available at http://www.ansi.org/.
STR
The C++ Programming Language, Appendix B - Compatibility Bjarne Stroustrup. Third ed., 1997, AT&T. Available in PDF format at http://www.research.att.com/~bs/3rd_compat.pdf.
번역 기준
저는 전문 번역가가 아닙니다. 이 글은 제가 보기에 적당한 수준으로만 번역했다는 것을 미리 일러둡니다. 우리글로 번역하기 애매한 부분은 작은 글꼴로 영어 원문을 함께 표기했습니다.
번역: implicit, implicitly, explicit, explicitly
사전상 함축적implicit 또는 함축적으로implicitly, 그리고 명백한explicit 또는 명백하게explcitly라고 번역할 수 있습니다.
사실상, C 언어에서 implicit 또는 implicitly란 용어는, 프로그래머가 직접 코드상에 어떠한 행위를 하지 않더라도, 내부적으로 알아서 이루어지는 행위를 나타내기 위해 씁니다. 이와 반대로, 프로그래머가 직접 해당 행위를 명시할 경우, explicit 또는 explicitly라고 표현합니다.
번역: implementation
표준 문서에는, 'implementation'이라는 단어가 나오며, 이는 대충, 표준을 구현한 컴파일러 또는 프로그램을 뜻합니다. 어떤 책에서는 이를 '구현체'라고 부르기도 하지만, (글쓴이의 기준에) 어색해 보이기 때문에, 혼동의 여지가 없는 한, 그냥 'C 컴파일러' 또는 '특정 C 컴파일러'라고 부르겠습니다. 예를 들어, 아래 영어 원문은:
The implementation is free to use a different underlying primitive
integer type for each enumerated type.
아래처럼 번역했습니다:
C 컴파일러는 각 열거형 타입에 대해 서로 다른 내부 단순 정수 타입underlying primitive integer type을 쓸 수 있습니다.
번역: identifier
특별히 구분할 상황이 아닌 경우, 'identifier'는 '이름'으로 번역합니다. 'name'과 구분할 상황이 필요한 경우, 전자는 '이름identifier', 후자는 '이름name'으로 씁니다.
번역: specifier
앞 명사에 따라 '지정자' 또는 '변환자'로 번역했습니다.
format specifier | 형 변환자 |
static specifier |
static 지정자 |
번역: constant, literal
둘 다 '상수'로 번역하고, 필요시 '상수literal'처럼 표기합니다.
번역: qualifier
제한을 가한다는 뜻으로 한정사(限定詞)란 용어가 적당해 보여서, 한정사qualifier로 번역합니다.
번역: expression
수식expression이라고 번역합니다.
영어 원본: 감사의 글
이 문서의 초안에 많은 도움을 준 분들에게 감사드립니다. 특히 다음과
같이 저[David R. Tribble]에게 제안/정정 사항을 이메일로 보내주신
분들과 comp.std.c
와 comp.std.c++
뉴스 그룹에서 답글을 주신
분들에게 감사드립니다.
- Nelson H. F. Beebe
- Greg Brewer
- David Capshaw
- Steve Clamage
- Yaakov Eisenberg
- Clive D. W. Feather
- Francis Glassborow
- Doug Gwyn, gwyn@arl.mil 또는 dagwyn@home.com.
- James Kanze
- Matt Seitz
- Vesa A J Karvonen
- Nick Maclaren
- Joe Maun
- Gabriel Netterdag
- Cesar Quiroz
- Bjarne Stroustrup, bs@research.att.com 그리고 www.research.att.com/~bs.
- Keith Thompson
- Martin van Loewis
- Daniel Villeneuve
- Bill Wade
영어 원본: 버전 히스토리
아래 내용은 영어 원문에 대한 버전 히스토리revision history입니다:
1.0, 2001-08-05 Completed revision. 0.12, 2000-11-13 Minor corrections made. Better HTML anchor names. 0.11, 2000-09-20 Sixth public review revision. Added ISO section reference numbers to most of the sections. 0.10, 2000-07-30 Sixth public review revision. 0.09, 2000-02-17 Fifth public review revision, still incomplete. 0.08, 1999-10-31 Fourth public review revision, still incomplete. Minor corrections made. Changed "C9X" to "C99" after the ratification of ISO C-1999. 0.07, 1999-10-13 Third public review revision, still incomplete. 0.06, 1999-10-05 Second public review revision, still incomplete. 0.05, 1999-09-14 First public review revision, still incomplete. 0.00, 1999-08-24 First attempt, incomplete.
영어 원본: 저작권
영어 원문에 대한 저작권은 David R. Tribble씨에게 있습니다. 아래 내용은 영어 원문에 대한 저작권에 관한 내용을 번역한 내용입니다. 한글 번역본에 대한 저작권은 이 글의 첫 부분을 참고하기 바랍니다.
Copyright © 1999-2001 by David R. Tribble, all rights reserved.
이 문서를 언급하거나 이 문서로 링크를 만드는 것은 저자의 허락 없이 가능합니다. 저자의 이름을 언급한다는 전제로, 저자의 허락없이 이 문서의 일부를 사용하거나 인용할 수 있습니다. 저자와 저작권 안내문이 변경되지 않고 출력된다는 전제로, 이 문서를 출력하고 배포하는 일은, 저자의 허락없이 가능합니다.
글쓴이의 이메일 주소는 david@tribble.com이며 홈페이지는 david.tribble.com입니다.