2017-09-08 23 views
5

この質問は、構造体オフセットを使用したポインタ演算を使用して派生したポインタに関するものです。C++での構造体オフセットとポインタの安全性

は、次のプログラムを考えてみましょう:

#include <cstddef> 
#include <iostream> 
#include <new> 

struct A { 
    float a; 
    double b; 
    int c; 
}; 

static constexpr auto off_c = offsetof(A, c); 

int main() { 
    A * a = new A{0.0f, 0.0, 5}; 
    char * a_storage = reinterpret_cast<char *>(a); 
    int * c = reinterpret_cast<int *>(a_storage + off_c)); 

    std::cout << *c << std::endl; 

    delete a; 
} 

このプログラムは、デフォルトの設定とC++ 11標準を使用して、仕事と私がテストしたコンパイラに期待される結果を与えることが表示されます。

(代わりにreinterpret_castの私たちが代わりにchar *static_castvoid *を使用し、密接に関連するプログラム、 は普遍的に認められていない。gcc 5.4がvoidポインタとポインタ演算に関する警告を発行し、clang 6.0void *とポインタ 算術演算であることを言いますエラー)

このプログラムは、C++標準に従って明確に定義された動作をしていますか?

回答は、実装がリラックスしているか厳密なポインタの安全性([basic.stc.dynamic.safety])を持っているかによって異なりますか?

+0

これは宿題ですか? –

+2

@ GarrGodfrey:私は驚いています... –

+2

なぜC++のメンバ機能へのポインタを使用してください。これは 'offsetof'の「クリーンな」代替手段ですか? –

答えて

8

コードに基本的なエラーはありません。

Aが普通のデータでない場合、上記はUB(C++ 17以前)であり、条件付きでサポートされています(C++ 17以降)。

char*int*auto*に置き換えることもできますが、それはスタイルのことです。

メンバへのポインタは、まったく同じことを型セーフな方法で行います。ほとんどのコンパイラは、メンバへのポインタをその型のメンバのオフセットとして実装しています。しかし、ポッド以外の構造でもどこでも動作します。

脇:offsetofが標準でconstexprであるという保証はありません。 ;)

いずれの場合にも、交換:

static constexpr auto off_c = offsetof(A, c); 

static constexpr auto off_c = &A::c; 

auto* a_storage = static_cast<char *>(a); 
    auto* c = reinterpret_cast<int *>(a_storage + off_c)); 

auto* c = &(a->*off_c); 

これをC++の方法で実行します。

+0

non-PODタイプでoffsetofを使用すると、Clangはあなたに怒鳴ります。そうすれば、少なくともいくらか近づいていることを示すヒントが得られます。https://godbolt.org/g/6jLnM6 - on on on非標準レイアウトタイプ 'A' [-Winvalid-offsetof] – xaxxon

+1

3.7.4.3 [basic.stc.dynamic.safety]でポインタが安全に導出されると言うのは、厳密なポインタの安全性がない場合、ポインタはそのような場所から来ていなければ無効です。 5.7ポインタの算術演算では、配列内で通常の算術演算を行うことができると言われていますが、何も表示されません。私はそれが、または構造体のオフセット算術が仮説的な "厳密な" implsでOKでない場合、または私が5.7を読んでいない場合、(n4296) –

+0

と答えている問題は、リラックスしたimplsでokです。厳密なimplで未定義の動作をしています。私は、配列にポインタを指さないインデックスにインデックスを追加すると、自動的に厳密な意味では安全ではなく、結果は安全に派生したptrではないと思います。また、無効なptrの逆参照はubです。私はこれが何らかの形でドラフトエラーでなければならないと思います。しかし、私は完全に間違っている可能性があります:) –

4

構造がの標準レイアウトであることを確認してください。これはstd::is_standard_layout<>で再確認できます。以下のような構造体にこれを適用しようとすると

struct A { 
    float a; 
    double b; 
    int c; 
    std::string str; 
}; 

は、文字列が関連性の構造体の一部を過ぎている場合でも、違法だろう。

編集アプト懸念何イム

相続人:3.7.4.3に[basic.stc.dynamic.safety]それはポインタが安全に導出されたと言うだけの場合(条件)と我々が持っている場合は、厳密なポインタの安全性そのような場所から来ていなければ、ポインタは無効です。 5.7ポインタの算術演算では、配列内で通常の算術演算を行うことができると言われていますが、何も表示されません。イムは、私はそれを考える方法で関連するこのありえないがあるかどうかを把握しようと、または構造体は、算術演算をオフセットた場合は、仮想的な「厳しい」implsでOKではないか、私は間違っ5.7(n4296)

あなたを読めばポインタを算術的に処理している場合は、配列charで実行しています。サイズは少なくともsizeof(A)です。 - 安全に由来ポインタの明確に定義されたポインタ変換(4.10、5.4)の結果

:あなたが第二の部材に再びキャストするとその後

、あなたは(2.4)の下で覆われています値;

+0

Ok、ポインタのタイプはchar型ですが、5.7.4では配列の算術についてのブロックです。ポインタは配列オブジェクトの要素を指しています... "という意味ではここに配列オブジェクトはありません。 Thatsは厳密なエイリアシングの問題にも関連しています。オブジェクトは、言語によって作成されたときにのみ存在し、キャストによって存在するようには思われません。だから、これは配列内の安全なポインタ操作として数えられないのですか?私は標準の観点から、ここには配列がないと思います。 Idkは確かにthoです。 –

1

あなたは前提条件を検討する必要があります。

仮定#1)offsetofは、正しいオフセットをバイト単位で返します。これは、クラスが "標準レイアウト"と見なされた場合にのみ保証されます。これは、仮想メソッドがないなどのいくつかの制限があり、複数の継承を回避します。この場合は問題ありませんが、一般的には必ず。

仮定#2)charはバイトと同じサイズです。 Cでは、これは定義によるものなので、安全です。

仮定#3)offsetofは、データの先頭からではなく、クラスからポインタへの正しいオフセットを与えます。これは基本的に#1と同じですが、vtableは確かに問題になる可能性があります。ここでも、標準レイアウトでしか動作しません。

+2

#1については、タイプに標準レイアウトがあるかどうかを確認することができます。http://en.cppreference.com/w/cpp/types/is_standard_layout –

+0

non-SLタイプでoffsetofを呼び出すと警告が表示されます – xaxxon

関連する問題