2017-11-16 8 views
4

uint32_tの値表現はuint8_t配列にコピーされます。これはstd::memcpyによって行われます。私がC++標準を理解しているので、これは完全に合法です。タイプTのオブジェクトにT*でアクセスしています。unsigned char*にキャストされています。エイリアシングの問題はなく、位置合わせの問題はありません。std :: memcpyまたは明示的なchar値の代入 - C++での等価および正当性

他の方法ではあまり明らかではありません。 unsigned char*Tのオブジェクト表現にアクセスしています。これは正当なものです。しかし、という語にはにアクセスすると、が変更されます。

もちろん、エイリアシングやアライメントの問題はありません。しかし、バッファーsの値が外部ソースからのものであれば、問題はあります。正しいエンディアンを保証し、トラップ表現を省略する必要があります。右エンディアンをチェックすることができますので、解決できます。 しかし、トラップ表現はどうですか?どうすればそれを避けることができますか?またはuintタイプには、doubleというよりも、トラップ表現がありませんか?

の値をuint_tオブジェクトにシフトする別の(より準拠した)方法があります。我々はまだエンディアンに従わなければならないが、これは安全にトラップ表現を省略するべきである。

しかし、小さなμC(8ビット)で大きなタイプをシフトするとかなり高価になることがあります。

次の質問は、2番目の試行(コードの下を参照)が合法性と機能性に関するmemcpyのアプローチと同等であるかどうかです。さて、それはmemcpyバージョンがよりオプティマイザに優しいようです。

#include <cstdint> 
#include <cstring> 
#include <cassert> 

typedef uint32_t utype; 

constexpr utype value = 0x01020304; 

int main() { 
    utype a{value}; 
    utype b{0}; 
    uint8_t s[sizeof(utype)]{}; 

    // first  
    std::memcpy(s, &a, sizeof(utype)); 
    assert(s[0] == (value & 0xff)); 

    std::memcpy(&b, s, sizeof(utype)); 
    assert(b == value); 

    // second  

    const uint8_t* ap = reinterpret_cast<const uint8_t*>(&a); 
    s[0] = ap[0]; // explicitly legal in C++ 
    s[1] = ap[1]; 
    s[2] = ap[2]; 
    s[3] = ap[3]; 
    assert(s[0] == (value & 0xff)); 

    uint8_t* bp = reinterpret_cast<uint8_t*>(&b); 
    bp[0] = s[0]; // same as memcpy or ist this UB ? 
    bp[1] = s[1]; 
    bp[2] = s[2]; 
    bp[3] = s[3]; 
    assert(b == value); 
} 
+0

'std :: memcpy'を実行するときにポインタが別の型を指しているオブジェクトにアクセスしていないときは、ポインタのポインティングされた型のオブジェクトにアクセスしていますが、ソースオブジェクトと同じ値を表します。 –

+3

'typedef uint32_t utype;を使わないでください。コードが読みにくくなります。私は他人の名前で話したくないので、私は自分のために話しますが、もっとプログラマにも当てはまると思います。私が 'utype a; 'を見ると、' utype'が何であるかを知るために、余分な認知的なステップが私の心の中にあります。そして、私の心の中でコード全体を読んでいる間に、「utype」は 'std :: uint32_t'、バックグラウンドプロセスは' utype'は 'std :: uint32_t'; ...." on repeatです。しかし、もし私が 'std :: uint32_t a;'を参照すると、私はそれについて考えることさえしません。私はただ瞬時に、ほとんど本能的に、それが何であるか知っています。 – bolov

+1

* access *は読み取りまたは書き込みを意味します –

答えて

3

しかし、用語のアクセスは変更含まれていますか?

はい。

注:実際にmemcpyは概念的に何をしていますか。バイトを狭い文字オブジェクトのように変更します。 これが可能でない場合、memcpyは標準のC++で実装できませんでした。

しかし、トラップ表現はどうですか?どうすればそれを避けることができますか?

これは非常に難しいです。トラップ表現がわかっている場合は、トラップ表現を持つ型の値を使用する前に、オブジェクトの狭いキャラクタビューを使用してテストする必要があります。トラップ表現を扱う標準的な方法があるかどうかはわかりません。

この問題を解決するには、std::is_trap<T>(void*)の特性があるはずですが、私が知る限りではありません。

私は、別の(より適合性の高い)方法がuint8_t値をuint_tオブジェクトにシフトすることを知っています。我々はまだエンディアンに従わなければならないが、これは安全にトラップ表現を省略するべきである。

外来値がトラップ表現である場合、その値はおそらく表現できない可能性があります。そのため、このような場合にオーバーフローなどの問題が発生する可能性があります。

シフトとmemcpyの違いは、シフトが既知のエンディアンをネイティブエンディアンに変換し、ソースがすでにネイティブエンディアンを持っているときにmemcpyが機能することです。


それはuint8_tunsigned charの別名であったことを保証された場合、第2のスニペットは明確に定義されるであろうとのmemcpyと機能的に同等。私はそれが保証されているかどうかは分かりませんが、確かに一般的です。狭い文字型だけがポインタエイリアシング規則の例外を持ちます。


assert(s[0] == (value & 0xff)); 

このアサートがCPUのエンディアンに依存しています。

+0

memcpyを標準のC++で実装できるかどうかは無関係です(標準ではそのようなものは要求されません)。 –

+0

@ M.Mそして、そのようなことが必要であることを意味するものではありません。それが価値あるものであれば、memcpyをC++で実装できるようにすることは良い選択だと私は考えています。 – user2079303

+0

私は文章を別のノートに分けて、それが引数として混乱しないようにしました。 – user2079303

関連する問題