CとC ++の両方で有効なコードは、各言語でコンパイルしたときに異なる動作を生成できますか?


664

CとC ++には多くの違いがあり、すべての有効なCコードが有効なC ++コードであるとは限りません。
(「有効」とは、動作が定義された標準コードを意味します。つまり、実装固有/未定義などではありません。)

CとC ++の両方で有効なコードが、各言語の標準コンパイラーでコンパイルしたときに異なる動作を生成するシナリオはありますか?

それを合理的/有用な比較にするために(私は質問で明らかな抜け穴を見つけようとするのではなく、実際に役立つ何かを学ぼうとしています)、仮定しましょう:

  • プリプロセッサに関連するものはありません(つまり#ifdef __cplusplus、、プラグマなどのハックがないことを意味します)
  • 実装で定義されたものは両方の言語で同じです(たとえば、数値制限など)。
  • 各標準のかなり最近のバージョン(たとえば、C ++ 98およびC90以降)を比較しています。
    バージョンが重要な場合は、それぞれのバージョンの動作が異なることを説明してください。

11
ちなみに、CとC ++を同時に使用する方言でプログラミングすると便利です。私はこれを過去に行ったことがあり、現在のプロジェクトの1つはTXR言語です。興味深いことに、Lua言語の開発者は同じことを行い、この方言を「Clean C」と呼びます。コンパイル時間のチェックが改善され、C ++コンパイラから役立つ診断が追加されるという利点がありますが、Cの移植性は維持されます。
Kaz

9
これにはより多くの見解と賛成投票があるため、私は古い質問をこの質問にマージしました。これはまだ非建設的な質問の例ですが、はい、それはSOユーザーに何かを教えるので、それはかなり境界線です。マージ前の質問の状態を反映するためだけに建設的ではないので、それを閉じます。同意せずに再開してください。
ジョージストッカー2012年

13
客観的に「はい」の後に例を挙げて回答できると思うので、再開するよう投票します(以下で証明されます)。そこから関連する行動を学ぶことができるという点で建設的だと思います。
アンダースアベル

6
@AndersAbel回答の純粋な数はすべて正解ですが、それがメークリストの質問のままであることを明確に示しています。リストを入手せずにこの質問をする方法はありませんでした。
dmckee ---元モデレーターの子猫

2
@dmckeeそれの価値について、私はあなたに同意します。しかし、C ++タグの人々は...言いましょう... feistyです。
ジョージストッカー

回答:


397

以下は、CとC ++で有効ですが、CとC ++では異なる値にiなります(ほとんどの場合)。

int i = sizeof('a');

違いの説明については、C / C ++の文字サイズ( 'a')を参照してください。

この記事からの別のもの:

#include <stdio.h>

int  sz = 80;

int main(void)
{
    struct sz { char c; };

    int val = sizeof(sz);      // sizeof(int) in C,
                               // sizeof(struct sz) in C++
    printf("%d\n", val);
    return 0;
}

8
間違いなくこれを期待していませんでした!私はもう少し劇的なものを望んでいましたが、これはまだ役に立ちます、ありがとう。:) +1
user541686 2012年

17
+1 2番目の例は、C ++がstruct構造体名の前に必要としないという事実のための良い例です。
セスカーネギー

1
@Andrey私はしばらく前に同じことを考えてテストしましたが、期待に反してGCC 4.7.1でstdなしで動作しました。それはGCCのバグですか?
セスカーネギー

3
@SethCarnegie:非準拠のプログラムが機能する必要はありませんが、機能することも保証されていません。
Andrey Vihrov、

3
struct sz { int i[2];};CとC ++ 異なる値を生成する必要があることを意味します。(一方、sizeof(int)== 1のDSP は同じ値を生成できます)。
Martin Bonnerはモニカの

464

C90が宣言されていない関数の呼び出しを許可するという事実と同様に、CとC ++の関数呼び出しとオブジェクト宣言の違いを利用する例を次に示します。

#include <stdio.h>

struct f { int x; };

int main() {
    f();
}

int f() {
    return printf("hello");
}

C ++では一時fファイルが作成および破棄されるため、これは何も出力しませんが、C90では、hello宣言せずに関数を呼び出すことができるため、出力します。

名前が気になった場合 fが2度使用されているので、CおよびC ++標準ではこれを明示的に許可しており、オブジェクトを作成するにstruct fは、構造が必要な場合は曖昧さをなくすかstruct、関数が必要な場合はオフにする必要があります。


7
「int f()」の宣言が「int main()」の定義の後にあるため、厳密にCで言えばこれはコンパイルされません:)
Sogartar

15
@ソガタール、本当に?codepad.org/STSQlUhh C99コンパイラーは警告を出しますが、それでもコンパイルできます。
jrajav

22
C関数の@Sogartarは暗黙的に宣言できます。
Alex B

11
@AlexB C9​​9およびC11にはありません。

6
@jrajavこれらはC99コンパイラではありません。C99コンパイラは、宣言されていない識別子を構文エラーとして検出します。それを行わないコンパイラは、C89コンパイラか、先行標準または別の種類の非準拠コンパイラです。

430

C ++とC90の場合、実装が定義されていない異なる動作を取得する方法が少なくとも1つあります。C90には単一行のコメントはありません。少し注意すれば、それを使用して、C90とC ++でまったく異なる結果の式を作成できます。

int a = 10 //* comment */ 2 
        + 3;

C ++では、//行から行末までのすべてがコメントなので、これは次のように機能します。

int a = 10 + 3;

C90には単一行のコメントがないため/* comment */、コメントのみです。最初/2はどちらも初期化の一部であるため、次のようになります。

int a = 10 / 2 + 3;

したがって、正しいC ++コンパイラーは13を提供しますが、厳密に正しいC90コンパイラー8を提供します。もちろん、ここでは任意の数値を選択しました。必要に応じて他の数値を使用できます。


34
WHOAこれはすごいです!考えられるすべてのことの中で、コメントが行動を変えるために使用できるとは思いもしなかったでしょう。+1
user541686 2012年

89
がなくても210 / + 3どちらが有効であるかが読み取られます(単項+)。
Benoit

12
楽しみのために、CとC ++の両方が異なる算術式を計算し、同じ結果に評価するように変更します。
ライアンC.トンプソン

21
@RyanThompsonささいなこと。s /
2/1

4
@Mehrdad私は間違っているか、コメントはプリプロセッサに関連していますか?したがって、それらはあなたの質問から可能な答えとして除外されるべきです!;-)
Ale

179

C90対C ++ 11(intdouble):

#include <stdio.h>

int main()
{
  auto j = 1.5;
  printf("%d", (int)sizeof(j));
  return 0;
}

Cではauto、ローカル変数を意味します。C90では、変数や関数の型を省略してもかまいません。デフォルトはintです。C ++ 11ではauto完全に異なる何かを意味し、初期化に使用された値から変数の型を推測するようコンパイラーに指示します。


10
C90はauto
セスカーネギー、

22
@SethCarnegie:ええ、それはストレージクラスです。これを省略した場合、デフォルトでそれが行われるため、誰もそれを使用せず、意味を変更しました。intデフォルトだと思います。これは賢いです!+1
user541686 2012年

5
C11は暗黙的ではありませんint
R .. GitHub ICE HELPING ICEの停止2012年

23
@KeithThompsonああ、私はあなたが推測したことを意味していると思いますint。それでも、多くのレガシーコードが存在し、市場のリーダーがまだC99を実装しておらず、実装する意図もない現実の世界では、「古いバージョンのC」の話はばかげています。
ジムバルター、2012年

11
「すべての変数は明示的なストレージクラスを持つ必要があります。本当に、上位の管理者です。」
btown

120

私がまだ言及していない別の例は、プリプロセッサの違いを強調しています。

#include <stdio.h>
int main()
{
#if true
    printf("true!\n");
#else
    printf("false!\n");
#endif
    return 0;
}

これにより、Cでは「false」、C ++では「true」が出力されます。Cでは、未定義のマクロは0と評価されます。C++では、1つの例外があります。「true」は1と評価されます。


2
面白い。この変更の根拠を知っている人はいますか?
2014

3
「true」はキーワード/有効な値なので、「真の値」と同様に(正の整数のように)trueと評価されます。C ++でも#define true falseを実行して「false」を出力することもできます;)
CoffeDeveloper '30

22
#define true false ಠ_ಠ
ブライアン・ベッチャー

2
@DarioOOはそのような再定義をUBにもたらしませんか?
Ruslan

3
@DarioOO:はい、あなたは間違っています。キーワードの再定義は許可されていません。罰は運命のままです(UB)。プリプロセッサは、コンパイルの別個のフェーズであることには耐えられません。
デデュプリケータ

108

C ++ 11標準ごと:

a。カンマ演算子は、Cで左辺値から右辺値への変換を実行しますが、C ++では実行しません。

   char arr[100];
   int s = sizeof(0, arr);       // The comma operator is used.

C ++ではこの式の値は100になり、Cではこれはになりますsizeof(char*)

b。C ++では、列挙子のタイプはその列挙型です。Cでは、列挙子のタイプはintです。

   enum E { a, b, c };
   sizeof(a) == sizeof(int);     // In C
   sizeof(a) == sizeof(E);       // In C++

これは、sizeof(int)と等しくない可能性があることを意味しsizeof(E)ます。

c。C ++では、空のparamsリストで宣言された関数は引数を取りません。Cでは、空のparamsリストは、関数paramsの数とタイプが不明であることを意味します。

   int f();           // int f(void) in C++
                      // int f(*unknown*) in C

最初のものも、Alexeyのように実装定義されています。しかし、+ 1。
セスカーネギー、

1
@Seth、上記のすべての資料は、C ++ 11標準のAnnex C.1から直接取得されています。
Kirill Kobelev、

はい。ただし、実装で定義されています。sizeof(char*)最初の例では、CとC ++で同じ監視可能な動作が生成されます(つまり、取得方法はs異なりますが、s最終的には100になります)。OPは、言語弁護士の回答を避けたいだけだったため、このタイプの実装定義の動作は問題ないと述べたため、最初の問題は例外で問題ありませんでした。しかし、2つ目はいずれにしても良いです。
セスカーネギー

5
簡単な修正プログラムがあります-ちょうどに例を変更しますchar arr[sizeof(char*)+1]; int s = sizeof(0, arr);
Mankarse

5
実装定義の違いを回避するには、を使用することもできますvoid *arr[100]。この場合、要素は同じ要素へのポインタと同じサイズであるため、2つ以上の要素がある限り、配列は最初の要素のアドレスより大きくなければなりません。
finnw

53

このプログラムは1、C ++およびCで出力します0

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int d = (int)(abs(0.6) + 0.5);
    printf("%d", d);
    return 0;
}

あるので、これが起こるdouble abs(double)過負荷がC ++にして、abs(0.6)戻って0.6Cにそれは返しながら0起動する前にあるため、暗黙的なダブルツーint型の変換int abs(int)。Cでは、を使用fabsするにはを使用する必要がありますdouble


5
その問題で他の誰かのコードをデバッグしなければなりませんでした。ああ、私はそれが好きだった。とにかく、あなたのプログラムはC ++でも0を出力しています。C ++ヘッダ「cmath」0 returnin比較最初の参照を使用する必要がideone.com/0tQB2G 1復帰2 1 ideone.com/SLeANoを
CoffeDeveloper

デバッグでこの違いを見つけたのは私だけではないというのはうれしいです。VS2013でテストしたところ、このコンテンツのファイルのみの空の場合、拡張子が.cppの場合は1、拡張子が.cの場合は0が出力されます。<math.h>がVSに間接的に含まれているようです。
Pavel Chikulaev 14

VS C ++のように見えますが、<math.h>にはC ++のものがグローバル名前空間に含まれていますが、GCCには含まれていません。ただし、どちらが標準的な動作なのかは不明です。
Pavel Chikulaev 14

2
この特定のコードサンプルは実装に依存 stdlib.habs(int)ていabs(long)ます。バージョンabs(double)はによって宣言されmath.hます。したがって、このプログラムはabs(int)バージョンを呼び出す場合があります。stdlib.hmath.h含まれるようにするかどうかは、実装の詳細です。(abs(double)呼び出されればバグだと思いますが、他のaspecsはmath.h含まれていませんでした)。
MM

1
二次的な問題は、C ++標準に<math.h>は、インクルードには追加のオーバーロードも含まれると記載されているように見えることです。実際には、フォーム<cmath>を使用しない限り、すべての主要なコンパイラにはこれらのオーバーロードが含まれていないことがわかります。
2016年

38
#include <stdio.h>

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

Cではsizeof(int)、現在のシステムの値が何であれ、これは通常、現在4一般的に使用されているほとんどのシステムで出力されます。

C ++では、これは1を出力する必要があります。


3
はい、「c」はCのintであり、C ++のcharであるということで、私は実際にこのトリックに精通していましたが、それでもここにリストするのは良いことです。
ショーン

9
それは興味深いインタビューの質問になります-特にCVにc / c ++の専門家を置く人々
マーティン・ベケット

2
ちょっと下手ですが。sizeofの目的は、型の大きさを正確に知る必要がないためです。
Dana the Sane

13
Cでは、値は実装定義であり、1が可能性です。(C ++では
Windowsプログラマ

3
実際には、どちらの場合も未定義の動作があります。%dはの正しい形式指定子ではありませんsize_t
R .. GitHub ICE HELPING ICEを停止する'27

37

別のsizeofトラップ:ブール式。

#include <stdio.h>
int main() {
    printf("%d\n", (int)sizeof !0);
}

sizeof(int)式の型がCの場合と同じですが、int通常はC ++では1です(必須ではありません)。実際には、ほとんど常に異なります。


6
1つ!で十分ですbool
Alexey Frunze 2012年

4
!! はブールへのint変換演算子です:)
EvilTeach

1
sizeof(0)は整数の右辺値な4ので、CとC ++の両方にあり0ます。sizeof(!0)である4Cにし、1C ++で。論理NOTは、bool型のオペランドを操作します。int値が0暗黙的にfalse(bool rvalue)に変換される場合は、反転されてになりtrueます。両方truefalseC ++でブール右辺値であり、sizeof(bool)です1。ただし、C では、int型の右辺値であるに!0評価され1ます。Cプログラミング言語には、デフォルトでブールデータ型はありません。
Galaxy

26

C ++プログラミング言語(第3版)には3つの例があります。

  1. sizeof( 'a')、@ Adam Rosenfieldが言及したとおり。

  2. // 非表示のコードを作成するために使用されているコメント:

    int f(int a, int b)
    {
        return a //* blah */ b
            ;
    }
  3. あなたの例のように、スコープなどの中で何かを隠す構造体など。



21

C ++標準でリストされているもう1つ:

#include <stdio.h>

int x[1];
int main(void) {
    struct x { int a[2]; };
    /* size of the array in C */
    /* size of the struct in C++ */
    printf("%d\n", (int)sizeof(x)); 
}

あなたはパディングの違いを得るのですか?
v.oddou 2016

申し訳ありませんがx、上にもう1つあります。あなたは「配列a」と言ったと思いました。
v.oddou 2016

20

Cのインライン関数はデフォルトで外部スコープに設定されますが、C ++のインライン関数はそうではありません。

以下の2つのファイルを一緒にコンパイルすると、GNU Cの場合は「I am inline」が出力されますが、C ++の場合は何も出力されません。

ファイル1

#include <stdio.h>

struct fun{};

int main()
{
    fun();  // In C, this calls the inline function from file 2 where as in C++
            // this would create a variable of struct fun
    return 0;
}

ファイル2

#include <stdio.h>
inline void fun(void)
{
    printf("I am inline\n");
} 

また、C ++ は、デフォルトのCとは異なり、明示的に宣言さconstれていstaticない限り、すべてのグローバルを暗黙的に扱います。externextern


私は本当にそうは思いません。おそらくあなたは要点を逃しました。コードを有効なc ++にするためだけに使用されるstruct stの定義についてではありません。重要なのは、cとc ++でのインライン関数の異なる動作を強調することです。同じことがexternにも当てはまります。これらはいずれのソリューションでも説明されていません。
fkl

2
インライン関数の異なる動作は何externですか?それはここに示されていますか?
Seth Carnegie、

かなりはっきり書いてあります。「cのインライン関数はデフォルトで外部スコープになり、c ++の関数はそうではありません(コードはそれを示します。また、externがデフォルトであるCとは異なり、C ++は明示的にexternと宣言されていない限り、constグローバルをファイルスコープとして暗黙的に扱います。同様のその例を作成できます。」私は困惑しています-理解できませんか?
fkl

12
@fayyazkl示されている動作は、検索(struct funvs fn)の違いによるものであり、関数がインラインであるかどうかには関係ありません。inline修飾子を削除しても結果は同じです。
Alex B

3
ISO Cではこのプログラムはinline形式fun()が正しくありません。C99まで追加されませんでしたが、C99ではスコープ内のプロトタイプがないと呼び出されません。私はこの答えを想定して唯一のGNU Cに適用される
MM

16
struct abort
{
    int x;
};

int main()
{
    abort();
    return 0;
}

C ++では終了コード0、Cでは3の終了コードで戻ります。

このトリックはおそらくもっと興味深いことをするために使用できますが、Cにとって口当たりの良いコンストラクタを作成する良い方法を考えることができませんでした。可搬性の高い方法ではありませんが、渡されます。

struct exit
{
    int x;
};

int main()
{
    struct exit code;
    code.x=1;

    exit(code);

    return 0;
}

VC ++ 2005は、C ++モードでのコンパイルを拒否しましたが、「終了コード」がどのように再定義されたかについて不満を述べていました。(私が突然プログラム方法を忘れていない限り、これはコンパイラーのバグだと思います。)Cとしてコンパイルされた場合でも、プロセス終了コード1で終了しました。


残念ながら、exitを使用した2番目の例は、gccまたはg ++でコンパイルできません。しかし、それは良い考えです。
ショーン

1
exit(code)どうやらcode、型の変数の有効な宣言ですexit。(「最も厄介な解析」を参照してください。これは別の類似した問題です)。
user253751 2015年

16
#include <stdio.h>

struct A {
    double a[32];
};

int main() {
    struct B {
        struct A {
            short a, b;
        } a;
    };
    printf("%d\n", sizeof(struct A));
    return 0;
}

このプログラムは印刷します 12832 * sizeof(double)、C ++コンパイラを4使用してコンパイルした場合、およびCコンパイラを使用してコンパイルした場合に()をます。

これは、Cにはスコープ解決の概念がないためです。Cでは、他の構造に含まれる構造は、外部構造のスコープに入ります。


これは面白い!(32*sizeof(double)ただし、32ではなく、という意味だと思います:))
user1354557

3
あなたがでUBを取得していることに注意して印刷するsize_t%d
phuclv

7

CとC ++のグローバル名前空間の違いを忘れないでください。foo.cppがあるとします

#include <cstdio>

void foo(int r)
{
  printf("I am C++\n");
}

foo2.c

#include <stdio.h>

void foo(int r)
{
  printf("I am C\n");
}

ここで、main.cmain.cppがあり、どちらも次のようになっているとします。

extern void foo(int);

int main(void)
{
  foo(1);
  return 0;
}

C ++としてコンパイルすると、C ++グローバル名前空間のシンボルを使用します。Cでは、Cを使用します。

$ diff main.cpp main.c
$ gcc -o test main.cpp foo.cpp foo2.c
$ ./test 
I am C++
$ gcc -o test main.c foo.cpp foo2.c
$ ./test 
I am C

リンケージ仕様ですか?
user541686 2014年

名前マングリング。C ++名には接頭辞と接尾辞がありますが、Cにはありません
CoffeDeveloper 2014年

名前マングリングはC ++仕様の一部ではありません。Cでは禁止されていますか?
2015

5
これは未定義の動作です(の複数定義foo)。個別の「グローバル名前空間」はありません。
2016年

4
int main(void) {
    const int dim = 5; 
    int array[dim];
}

これは、C ++およびC99、C11、C17(C11、C17ではオプションですが)で有効であるという点で、かなり独特です。C89では無効です。

C99 +では、コンパイル時の型の代わりに実行時の型sizeof arrayがあり、Cの整数定数式ではないため、通常の配列に独自の特性を持つ可変長配列を作成します。C++では、型は完全に静的です。


ここに初期化子を追加しようとすると:

int main(void) {
    const int dim = 5; 
    int array[dim] = {0};
}

可変長配列は初期化子を持つことができないため、Cは有効ですがCは無効です。


0

これは、CおよびC ++の左辺値と右辺値に関係します。

Cプログラミング言語では、前置増分演算子と後置増分演算子の両方が左辺値ではなく右辺値を返します。つまり、=代入演算子の左側に置くことはできません。これらのステートメントは両方とも、Cでコンパイラエラーを発生させます。

int a = 5;
a++ = 2;  /* error: lvalue required as left operand of assignment */
++a = 2;  /* error: lvalue required as left operand of assignment */

ただし、C ++では、前インクリメント演算子は左辺値を返し、後置インクリメント演算子は右辺値を返します。つまり、プリインクリメント演算子を含む式は、=代入演算子の左側に配置できます。

int a = 5;
a++ = 2;  // error: lvalue required as left operand of assignment
++a = 2;  // No error: a gets assigned to 2!

これはなぜですか?ポストインクリメントは変数をインクリメントし、以前と同じように変数を返します、インクリメントが発生する。これは実際には単なる右辺値です。変数aの以前の値は一時的にレジスタにコピーされ、次にaがインクリメントされます。しかし、aの以前の値は式によって返され、それは右辺値です。変数の現在の内容を表しなくなりました。

前置インクリメントは、最初に変数をインクリメントし、その後、インクリメントが発生した後の変数を返します。この場合、変数の古い値を一時レジスタに格納する必要はありません。インクリメントされた後の変数の新しい値を取得するだけです。したがって、前置インクリメントは左辺値を返し、変数a自体を返します。この左辺値を別のものに割り当てることができます。これは次のステートメントのようです。これは、lvalueからrvalueへの暗黙の変換です。

int x = a;
int x = ++a;

前置インクリメントは左辺値を返すので、それに何かを割り当てることもできます。次の2つのステートメントは同じです。2番目の割り当てでは、最初にaが増分され、次にその新しい値が2で上書きされます。

int a;
a = 2;
++a = 2;  // Valid in C++.

3
ここには「有効なC」はありません。
o11c 2018年

0

空の構造体のサイズはCでは0、C ++では1です。

#include <stdio.h>

typedef struct {} Foo;

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

1
いいえ、違いは、Cにはコンパイラー拡張機能を除いて、空の構造がないことです。つまり、このコードは「CとC ++の両方で有効です」と一致しません
Antti Haapala
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.