web-dev-qa-db-ja.com

Cで匿名の構造体を返す方法は?

いくつかのコードを試してみたところ、次のコードがコンパイルされることがわかりました。

struct { int x, y; } foo(void) {
}

匿名fooを返すstructという名前の関数を定義しているようです。

今、私の質問は次のとおりです。それは私のコンパイラでコンパイルするだけですか、それともこの合法的なC(99)ですか?もしそうなら、returnステートメントの正しい構文は何ですか?どのようにして戻り値を変数に正しく割り当てることができますか?

34
Askaga

返される構造体は匿名の構造体ではありません。 C標準では、匿名の構造体を、タグを使用しない別の構造体のメンバーとして定義しています。返されるのはタグのない構造体ですが、メンバーではないため、匿名ではありません。 Gccは、名前<anonymous>を使用して、タグのない構造体を示します。

関数で同じ構造体を宣言しようとしているとしましょう。

struct { int x, y; } foo( void )  
{
    return ( struct { int x, y; } ){ 0 } ;
}

gccはそれについて不平を言います:タイプ 'struct <anonymous>'を返すときに互換性のないタイプですが、 'struct <anonymous>'が予期されていました

どうやらタイプは互換性がありません。標準を見ると、次のことがわかります。

6.2.7互換タイプと複合タイプ

1:2つのタイプは、タイプが同じであれば互換タイプがあります。 2つの型に互換性があるかどうかを判断するための追加の規則は、型指定子の6.7.2、型修飾子の6.7.3、および宣言子の6.7.6で説明されています。 さらに、2つの構造体、共用体、または列挙型が別々の変換で宣言されていますユニットは、タグとメンバーが次の要件を満たしている場合に互換性があります。一方がタグで宣言されている場合、もう一方も同じで宣言されます。鬼ごっこ。 両方がそれぞれの翻訳単位内のどこかで完了する場合、次の追加要件が適用されます:対応するメンバーの各ペアが互換性のある型で宣言されるように、メンバー間に1対1の対応がある;ペアの一方のメンバーがアライメント指定子で宣言されている場合、もう一方は同等のアライメント指定子で宣言されています。ペアの一方のメンバーが名前で宣言されている場合、もう一方は同じ名前で宣言されています。 2つの構造の場合、対応するメンバーは同じ順序で宣言されます。 2つの構造体または共用体の場合、対応するビットフィールドの幅は同じでなければなりません。 2つの列挙の場合、対応するメンバーは同じ値を持つ必要があります。

2番目の太字部分は、この例のように、両方の構造体にタグがない場合、その部分の後にリストされている追加の要件に従う必要があることを説明しています。しかし、最初の太字部分が別々の翻訳単位になければならないことに気付いた場合、例の構造体はそうではありません。そのため、互換性がなく、コードが無効です。

構造体を宣言してこの関数で使用する場合、両方の構造体が同じタグを持つ必要があるという規則に違反するタグを使用する必要があるため、コードを正確にすることは不可能です。

struct t { int x, y; } ;

struct { int x, y; } foo( void )   
{
    struct t var = { 0 } ;

return var ;
}

再びgccは不平を言います:タイプ 'struct t'を返すときに互換性のないタイプですが、 'struct <anonymous>'が予期されていました

24
2501

これは私のバージョンのGCCで機能しますが、完全なハックのようです。おそらく、一意の構造タグを生成するという複雑さを処理したくない自動生成コードで役立つかもしれませんが、私はその合理化を考え出すために、一種のストレッチをしています。

struct { int x,y; }
foo(void) {
   typeof(foo()) ret;
   ret.x = 1;
   ret.y = 10;
   return ret;
}

main()
{
   typeof(foo()) A;

   A = foo();

   printf("%d %d\n", A.x, A.y);
}

また、コンパイラに存在するtypeof()にも依存しています-GCCとLLVMはそれをサポートしているようですが、多くのコンパイラはサポートしていないと思います。

15
John Brennen

おそらく、明示的にreturn関数からのいくつかの集計値(typeof拡張機能を使用して型を取得しない限り)結果の)。

ストーリーの教訓は、anonymousstructを返す関数を宣言できたとしても、実用的に絶対にしないでください

代わりに、structとコードに名前を付けます。

struct twoints_st { int x; int y; };
struct twoints_st foo (void) {
   return ((struct twoints_st) {2, 3});
};

returnのない関数を使用することは、構文的には問題ありませんが、一般的には実行時の動作が定義されていないことに注意してください(たとえば、内部でexitを呼び出すことができます)。しかし、なぜあなたは(おそらく合法的)をコード化したいと思うでしょう:

struct { int xx; int yy; } bizarrefoo(void) { exit(EXIT_FAILURE); }

これは、ハックせずにC++ 14で匿名の構造体を返す方法です。
(おそらくC++ 11で十分でしょう)
私の場合、関数intersect()std::pair<bool, Point>を返しますが、あまり説明的ではないため、結果のカスタムタイプを作成することにしました。
個別にstructを作成することもできましたが、この特別な場合にのみ必要になるため、価値がありませんでした。それが、匿名の構造体を使用した理由です。

auto intersect(...params...) {
    struct
    {
        Point point;
        bool intersects = false;
    } result;

    // do stuff...

    return result;
}

そして今、醜いの代わりに

if (intersection_result.first) {
    Point p = intersection_result.second

私はもっ​​と格好良く見えるものを使うことができます:

if (intersection_result.intersects) {
    Point p = intersection_result.point;
3
Al.G.

または、無限再帰を作成することもできます。

struct { int x, y; } foo(void) {
   return foo();
}

これは完全に合法だと思います。

2

これはGCCの最新バージョンまで機能します。これは、マクロを使用して動的配列を作成する場合に特に役立ちます。例えば:

#define ARRAY_DECL(name, type) struct { int count; type *array; } name

次に、reallocなどを使用して配列を作成できます。これは、ANY型の動的配列を作成でき、すべてを作成する1つの方法があるため便利です。そうしないと、多くのvoid *し、キャストなどで実際に値を取得する関数を記述します。これらすべてをマクロでショートカットできます。それが彼らの美しさです。

1
Gophyr