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>&amp;&amp;</code></td></tr> <tr><td class="left"><code>andeq</code></td><td class="left"><code>&amp;=</code></td></tr> <tr><td class="left"><code>bitand</code></td><td class="left"><code>&amp;</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++ 코드로 컴파일 될 때, 함수 포인터는 함축적으로implicitly​extern "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"
    

    [ARM© Compiler toolchain Using the Compiler]

미리 정의된 이름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]

  • 역자 주: restrict 포인터

    restrict 키워드를 쓴 포인터의 경우, 이 포인터가 가리키는 오브젝트를 접근access⁠하기 위해서, 반드시 이 포인터만 쓸 수 있다는 것을 나타냅니다. 즉 다른 포인터를 써서 이 포인터가 가리키는 오브젝트에 접근할 수 없습니다.

    이는 컴파일러가 최적화를 수행하는 데, 도움이 됩니다. 즉, 컴파일러가 모든 경우를 다 분석하지 않더라도, (restrict 키워드를 쓴) 의존성 검사를 쉽게 할 수 있게 됩니다.

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 literalconst

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++ 뉴스 그룹에서 답글을 주신 분들에게 감사드립니다.

영어 원본: 버전 히스토리

아래 내용은 영어 원문에 대한 버전 히스토리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입니다.