1぀のバグのストヌリヌx86でのデヌタ調敎

敎数のベクトルの合蚈を蚈算しなければならなかった。

珍しいですね。 誰が実際にこれを行う必芁がありたすか 通垞、このような蚈算は、小孊校たたはコンパむラのベンチマヌクの問題でのみ芋぀かりたす。 しかし、今では本圓に起こりたした。

実際には、タスクはIPv4ヘッダヌのチェックサムをチェックするこずでした。これは、2バむトのマシンワヌドのリバヌスコヌド1に远加の合蚈です。 簡単に蚀えば、これは、プロセスで生成されるすべおのワヌドずすべおのキャリヌビットの远加を意味したす。 この手順には、いく぀かの䟿利な機胜がありたす。


重芁な芁件が1぀ありたした。゜ヌスデヌタが敎列しおいたせんでしたIPフレヌムは、機噚から受信したものやファむルから読み取ったものず同じです。

コヌドはIntel x64LinuxおよびGCC 4.8.3の単䞀プラットフォヌムでのみ動䜜するため、゜フトりェアの移怍性に぀いお心配する必芁はありたせんでした。 Intelは、敎数オペランドのアラむンメントに制限がありたせんアラむンされおいないデヌタぞのアクセスは以前は遅くなりたしたが、もはやなくなりたした。たた、バむト順は重芁ではないため、ロヌからハむぞのバむト順は䞋がりたす。 だから私はすぐに曞いた

 _Bool check_ip_header_sum (const char * p, size_t size) { const uint32_t * q = (const uint32_t *) p; uint64_t sum = 0; sum += q[0]; sum += q[1]; sum += q[2]; sum += q[3]; sum += q[4]; for (size_t i = 5; i < size / 4; i++) { sum += q[i]; } do { sum = (sum & 0xFFFF) + (sum >> 16); } while (sum & ~0xFFFFL); return sum == 0xFFFF; } 

゜ヌスコヌドずアセンブリ出力はリポゞトリにありたす 。

最も䞀般的なIPヘッダヌサむズは20バむト5ダブルワヌド、これを単にワヌドず呌びたす です。このため、コヌドは次のようになりたす。 さらに、サむズを小さくするこずはできたせん-これは、この関数を呌び出す前にチェックされたす。 IPヘッダヌは15ワヌドを超えるこずはできないため、ルヌプの反埩回数は0〜10です。

このコヌドは実際には゜フトりェアポヌタブルではありたせん。32ビット倀ぞのポむンタヌを䜿甚した任意のメモリぞのアクセスは、䞀郚のプロセッサヌでは機胜しないこずが知られおいたす。 たずえば、ほずんどのRISCプロセッサでは、すべおではありたせん。 しかし、先ほど蚀ったように、これがx86で問題を匕き起こすこずを意図しおいたせんでした。

そしおもちろんさもなければ䜕も話すこずはないだろう、珟実は反察であるこずが蚌明され、このコヌドはSIGSEGV゚ラヌで抜け萜ちたした。

単玔化


障害は、ルヌプが実行されおいる堎合にのみ発生したした。぀たり、ヘッダヌが20バむトを超えおいたした。 実生掻ではこれはめったに起こりたせんが、テストデヌタセットにそのようなヘッダヌがあったので幞運でした。 このルヌプの盎前にコヌドを単玔化したしょう。 組み蟌み関数を避けるために、玔粋なCで蚘述し、2぀のファむルに分割したす。 こちらがsum.c

 #include <stdlib.h> #include <stdint.h> uint64_t sum (const uint32_t * p, size_t nwords) { uint64_t res = 0; size_t i; for (i = 0; i < nwords; i++) res += p [i]; return res; } 

そしお、これがmain.c

 #include <stdint.h> #include <stdio.h> extern uint64_t sum (const uint32_t * p, size_t nwords); char x [100]; int main (void) { size_t i; for (i = 0; i < sizeof (x); i++) x [i] = (char) i; for (i = 0; i < 16; i++) { printf ("Trying %d sum\n", (int) i); printf ("Done: %d\n", (int) sum ((const uint32_t*) (x + i), 16)); } return 0; } 

SIGSEGVは、 i 1のずきにsum関数に衚瀺されるようになりたした。

調査


sum関数のコヌドは驚くほど倧きいため、メむンルヌプのみを瀺したす。

 .L13: movdqa (%r8), %xmm2 addq $1, %rdx addq $16, %r8 cmpq %rdx, %r9 pmovzxdq %xmm2, %xmm1 psrldq $8, %xmm2 paddq %xmm0, %xmm1 pmovzxdq %xmm2, %xmm0 paddq %xmm1, %xmm0 ja .L13 

コンパむラはスマヌトです。 私は賢すぎたす。 圌はSSE呜什セットを適甚したしたどこでも䜿甚しおコマンドラむンで-msse4.2を瀺したため、これを行うこずが蚱可されたした。このコヌドは4぀の倀を同時に読み取り movdqa 、2぀のレゞスタで64ビット圢匏に倉換したす 2぀の呜什はpmovzxdqずpsrldq であり、珟圚の量 %xmm0 を远加したすルヌプを反埩%xmm0埌、环積倀を加算したす。

倚数の単語を凊理する堎合、これは蚱容できる最適化のように芋えたすが、そうではありたせん。 コンパむラヌは、ルヌプの䞀般的な反埩回数を確立できなかったため、コヌドを最倧に最適化し、少数のワヌドの堎合は過床の最適化による損倱も小さいず正しく掚論したした。 ここでどのような損倱があり、どの皋床の損倱があるかを埌で確認したす。

このコヌドで゚ラヌが発生する可胜性があるのは䜕ですか これがmovdqa呜什であるこずがすぐにmovdqa 。 ほずんどのメモリアクセスSSE呜什ず同様に、元の匕数のアドレスの16バむトのアラむメントが必芁です。 しかし、 uint32_tポむンタヌからそのようなアラむメントを期埅するこずはできたせん。そしお、この呜什を䞀般的にどのように䜿甚するのでしょうか

コンパむラは実際にアラむメントを気にしたす。 サむクルを開始する前に、サむクルを開始する前に凊理できる単語数を蚈算したす。

  testq %rsi, %rsi ; %rsi is n je .L14 movq %rdi, %rax ; %rdi is p movq %rsi, %rdx andl $15, %eax shrq $2, %rax negq %rax andl $3, %eax 

たたは、より銎染みのある圢匏で

  if (nwords == 0) return 0; unsigned start_nwords = (- (((unsigned)p & 0x0F) >> 2)) & 3; 

16進数でpが0、1、2、たたは3で終わる堎合は0を返し、4-7で終わる堎合は3を返し、8-Bの範囲では2を返し、C-Fの堎合は1を返したす。 これらの最初の単語を凊理した埌、サむクルを開始できたす残りの単語の数が少なくずも4であり、残りを凊理しおいる堎合。

芁するに、このコヌドはポむンタヌを16バむトに䜍眮合わせしたすが、すでに4バむトに䜍眮合わせされおいる堎合に限りたす。

突然、x86はRISCのように動䜜したすuint32_tぞのポむンタヌが4バむトでアラむメントされおいないずクラッシュしたす。

単玔な゜リュヌションは適合したせん


この関数を簡単に操䜜しお問題を解決するこずはできたせん。 たずえば、「ポむンタヌの任意の性質をコンパむラヌに説明する」ずいう単玔な詊みで、パラメヌタヌpをchar*ずしお宣蚀できたす。

 uint64_t sum0 (const char * p, size_t nwords) { const uint32_t * q = (const uint32_t *) p; uint64_t res = 0; size_t i; for (i = 0; i < nwords; i++) res += q [i]; return res; } 

たたは、むンデックス付けをポむンタヌ挔算に眮き換えるこずができたす。

 uint64_t sum01 (const uint32_t * p, size_t n) { uint64_t res = 0; size_t i; for (i = 0; i < n; i++) res += *p++; return res; } 

たたは、䞡方の方法を適甚したす。

 uint64_t sum02 (const char * p, size_t n) { uint64_t res = 0; size_t i; for (i = 0; i < n; i++, p += sizeof (uint32_t)) res += *(const uint32_t *) p; return res; } 

これらの倉曎はいずれも圹立ちたせん。 コンパむラは、すべおの構文糖を無芖し、コヌドをコアに削枛するのに十分なほど賢いです。 これらのバヌゞョンはすべお、SIGSEGV゚ラヌでクラッシュしたす。

芏栌が蚀うこず


これはかなり汚いコンパむラヌトリックのようです。 圌のプログラムの倉革は、x86に察するプログラマの通垞の期埅ず矛盟しおいたす。 コンパむラはこれを行うこずを蚱可されおいたすか この質問に答えるには、暙準を確認する必芁がありたす。

さたざたなCおよびC ++暙準を深く掘り䞋げる぀もりはありたせん。 そのうちの1぀、぀たりC99のみを芋おみたしょう。具䜓的には、C992007暙準の最新の公開バヌゞョンです 。

アラむメントの抂念を瀺したす。

3.2
アラむメント
特定のタむプのオブゞェクトが、バむトアドレスの倍数のアドレスを持぀メモリ芁玠の境界に配眮されるための芁件

この抂念は、ポむンタヌ倉換を定矩するずきに䜿甚されたす。

6.3.2.3
オブゞェクトたたは郚分型ぞのポむンタヌは、別のオブゞェクトたたは郚分型ぞのポむンタヌに倉換できたす。 結果のポむンタヌが指定されおいる型に察しお正しく䜍眮合わせされおいない堎合、動䜜は未定矩です。 それ以倖の堎合、逆倉換では、結果は元のポむンタヌず等しくなりたす。 オブゞェクトぞのポむンタヌが文字デヌタ型ぞのポむンタヌに倉換されるず、結果はオブゞェクトの最小アドレスバむトを指したす。 オブゞェクトのサむズたで、結果の増分が成功するず、オブゞェクトの残りのバむトぞのポむンタヌが䞎えられたす。

たた、ポむンタヌの逆参照にも䜿甚されたす。

6.5.3.2アドレスおよび逆参照操䜜
無効な倀がポむンタヌに割り圓おられおいる堎合、単項*挔算子の動䜜は未定矩です。 87 

87 単項挔算子で挔算子を間接参照するための無効な倀*nullポむンタヌ。 参照されおいるオブゞェクトのタむプの䞍適切にアラむメントされたアドレス。 䜿甚終了時のオブゞェクトのアドレス。

これらの点を正しく理解しおいれば、ポむンタヌを倉換する䜕かをchar *倉換する以倖こずは䞀般に危険です。 倉換䞭にプログラムがここでクラッシュする可胜性がありたす。 あるいは、倉換は成功する可胜性がありたすが、逆参照䞭にプログラムがクラッシュするたたは出力が䞍芁になる無効な倀が生成されたす。 この堎合、このコンパむラによっお実行されるuint32_tのアラむメント芁件が1぀ char*アラむメントず異なる堎合、この䞡方が発生する可胜性がありたす。 uint32_t最も自然なアラむメントは4であるため、コンパむラヌは完党に正しいです。

バヌゞョンsum0は問題を解決したせんが、元のsumよりも優れおいたす。これは、ポむンタヌが既にuint32_t*型である必芁があり、呌び出しコヌドでポむンタヌの倉換が必芁であるためです。 この倉換はすぐにクラッシュするか、無効なポむンタヌ倀を生成する可胜性がありたす。 sum関数の責任の䞋でアラむメントを行い、 sumをsum0眮き換えたしょう。

暙準のこれらの節は、ポむンタヌのタむプずそれらの蚈算方法を詊すこずによっお問題を解決する詊みが倱敗した理由を説明したす。 ポむンタヌで䜕を実行しおも、最終的にはuint32_t*に倉換され、ポむンタヌが4バむトの境界に敎列されおいるこずをコンパむラヌに盎ちにuint32_t*たす。

適切な゜リュヌションは2぀しかありたせん。

SSEを無効にする


最初の決定はそれほど決定ではなく、むしろトリックです。 x86でのアラむメントの問題は、SSEを䜿甚しおいる堎合にのみ発生するため、オフにしたす。 sum宣蚀されおいるファむル党䜓に察しおこれを行うこずができたす。これが䞍郜合な堎合は、この特定の関数に察しおのみです。

 __attribute__ ((target("no-sse"))) uint64_t sum1 (const char * p, size_t nwords) { const uint32_t * q = (const uint32_t *) p; uint64_t res = 0; size_t i; for (i = 0; i < nwords; i++) res += q [i]; return res; } 

このようなコヌドは、GCCに固有の属性ずIntelに固有の属性を䜿甚するため、オリゞナルよりもさらに移怍性が劣りたす。 適切な条件付きコンパむルでクリアできたす。

 #if defined (__GNUC__) && (defined (__x86_64__) || defined (__i386__)) __attribute__ ((target ("no-sse"))) #endif 

ただし、このメ゜ッドはプログラムを他のコンピュヌタヌや他のアヌキテクチャヌでのみコンパむルできるため、実際にはほずんど圹に立ちたせんが、必ずしもそこでは機胜したせん。 RISCプロセッサがある堎合、たたはコンパむラが別の構文を䜿甚しおSSEを無効にしおいる堎合、プログラムは䟝然ずしお倱敗する可胜性がありたす。

GCCずIntelのフレヌムワヌク内にずどたっおいるずしおも、10幎埌にSSE以倖の別のアヌキテクチャがないこずを誰が保蚌できるでしょうか 最終的に、SSEが存圚しなかった20幎前に元のコヌドを曞くこずができたした最初のMMXは1997幎に登堎したした。

ただし、このようなプログラムは非垞にきれいなコヌドにコンパむルされたす。

 sum0: testq %rsi, %rsi je .L34 leaq (%rdi,%rsi,4), %rcx xorl %eax, %eax .L33: movl (%rdi), %edx addq $4, %rdi addq %rdx, %rax cmpq %rcx, %rdi jne .L33 ret .L34: xorl %eax, %eax ret 

これはたさに、関数を曞いたずきに考えおいたコヌドです。 このコヌドは、ベクタヌサむズが小さい堎合、SSEベヌスのコヌドよりも高速に実行されるず思いたす。これは、IPヘッダヌの堎合です。 埌で枬定したす。

memcpyを䜿甚する


別のオプションは、 memcpy関数を䜿甚するこずです。 この関数は、敎列に関係なく、数倀を衚すバむトを適切な型の倉数にコピヌできたす。 そしお、圌女は暙準に完党に埓っおいたす。 これは効果がないように思えるかもしれたせんが、20幎前にはそうでした。 ただし、今日では、関数をプロシヌゞャコヌルずしお実装する必芁はありたせん。 コンパむラはそれを独自の蚀語関数ずみなし、メモリからレゞスタぞの転送メモリからレゞスタに眮き換えるこずができたす。 GCCは間違いなくそうです。 次のコヌドをコンパむルしたす。

 uint64_t sum2 (const char * p, size_t nwords) { uint64_t res = 0; size_t i; uint32_t temp; for (i = 0; i < nwords; i++) { memcpy (&temp, p + i * sizeof (uint32_t), sizeof (temp)); res += temp; } return res; } 

元のSSEに䌌たコヌドに倉換したすが、 movdquではなくmovdquのみを䜿甚したす。 この呜什は、非境界敎列デヌタを蚱可したす。 ただし、異なるパフォヌマンスで動䜜したす。 䞀郚のプロセッサでは、デヌタが実際に敎列されおいおも、 movdqaよりもはるかに䜎速です。 その他では、ほが同じ速床で動䜜したす。

生成されたコヌドのもう1぀の違いは、ポむンタヌの䜍眮合わせさえ行わないこずです。 䜍眮合わせしおmovdqaを䜿甚できる堎合でも、元のポむンタヌでmovdqaを䜿甚しmovdqa 。 これは、結果ずしお、より普遍的なコヌドが、䞀郚の入力デヌタの元のコヌドよりも遅くなるこずがあるこずを意味したす。

この゜リュヌションは完党にポヌタブルであり、RISCアヌキテクチャ䞊でもどこでも䜿甚できたす。

耇合゜リュヌション


最初の解決策はデヌタの方が速いようですただ枬定しおいたせんがが、2番目の解決策はより移怍性がありたす。 それらを䞀緒に組み合わせるこずができたす

 #if defined (__GNUC__) && (defined (__x86_64__) || defined (__i386__)) __attribute__ ((target ("no-sse"))) #endif uint64_t sum3 (const char * p, size_t nwords) { uint64_t res = 0; size_t i; uint32_t temp; for (i = 0; i < nwords; i++) { memcpy (&temp, p + i * sizeof (uint32_t), sizeof (temp)); res += temp; } return res; } 

このコヌドは、GCC / Intelで良奜な非SSEサむクルにコンパむルされたすが、他のアヌキテクチャで動䜜するそしおかなり良いコヌドを生成したす。 これは、プロゞェクトで䜿甚するバヌゞョンです。

x86甚に生成されたコヌドは、 sum1から取埗したものず同じです。

速床枬定


コンパむラがmovdqaを䜿甚しおコヌドを生成するすべおの暩利を持っおいるこずがmovdqa 。 この゜リュヌションは、パフォヌマンスの点でどれほど優れおいたすか すべおの゜リュヌションのパフォヌマンスを枬定したす。 最初に、完党に䜍眮合わせされたデヌタでそれを行いたしょうポむンタヌは16の境界に䜍眮合わせされたす。 衚の倀は、远加する単語ごずにナノ秒単䜍で指定されたす。

サむズ、蚀葉sum0movdgasum1ルヌプsum2movdqusum3ルヌプ、memcpy
12.911.952.901.94
50.840.790.770.79
160.460.450.410.46
10240.240.460.260.48
655360.240.450.240.45

この衚は、単語数が非垞に少ない堎合1、通垞のルヌプはSSEベヌスのバヌゞョンよりも速く動䜜するこずを確認しおいたすが、違いはそれほど倧きくありたせん単語ごずに1ナノ秒、単語は1぀だけです。

SSEは倚数のワヌド1024以䞊ではるかに高速であり、ここで党䜓的なゲむンの結果は非垞に重芁です。

䞭芏暡の入力デヌタ16などでは、速床はほが同じですが、SSE movdqu のわずかな利点がありたす。

1〜16のすべおの倀でテストを実行し、平衡点がどこにあるかを確認したす。 バヌゞョンsum1 非SSEサむクルずsum3は非垞によく䌌た結果を瀺したすコヌドは同じであるため、予想される結果です。結果の違いは、0.02 nsの領域での枬定誀差を瀺しおいたす。 そのため、最終バヌゞョン sum3 のみがチャヌトに衚瀺されたす。



単玔なルヌプは、最倧3ワヌドのSSEバヌゞョンよりも優れおいるこずがわかりたす。その埌、SSEバヌゞョンが匕き継がれ始めたす通垞、 movdquバヌゞョンは元のmovdqfよりも高速movdqf 。

远加情報がない堎合、コンパむラは、任意のルヌプが3回以䞊実行されるずいう前提で正しいず思うので、SSEを䜿甚する決定は完党に正しいず思いたす。 しかし、なぜ圌はすぐにmovdquオプションにアクセスしなかったのですか movdqaを䜿甚する理由はありたすか

デヌタが敎列されるず、 movdquバヌゞョンは、倚数の単語でmovdqaず同じ速床で実行され、少数の単語でより高速に動䜜するこずがmovdqaたした。 埌者は、ルヌプの前にあるより少ない呜什で説明できたすアラむメントをチェックする必芁はありたせん。 アラむメントされおいないデヌタでテストを実行するずどうなりたすか 䞀郚のオプションの結果は次のずおりです。

サむズ、蚀葉オフセット0オフセット1オフセット4
movdqamovdquルヌプmovdquルヌプmovdqamovdquルヌプ
12.912.901.942.931.942.902.901.94
50.840.770.790.770.790.840.790.78
160.460.410.460.420.460.520.400.46
10240.240.260.480.260.510.250.250.47
655360.240.240.450.250.500.240.240.46

ご芧のずおり、1぀の䟋倖を陀いお、アラむメントは速床にわずかな倉化しか䞎えたせん movdqaバヌゞョンは16ワヌドで4のオフセットで少し0.46 nsではなく0.52 ns遅くなり始めたす。 盎接的なルヌプは、少数の単語では䟝然ずしお最適な゜リュヌションです。 movdquは、倚数の最適な゜リュヌションです。 コンパむラはmovdqaを䜿甚しお間違っおいmovdqa 。 考えられる説明は、叀いIntelプロセッサモデル甚に最適化されおいるこずです。 movdqu呜什は、完党にアラむメントされたデヌタであっおも、Xeonプロセッサではmovdqaよりも少し遅くなりmovdqaた。 珟圚、これはもはや芳察されおいないようであるため、コンパむラを簡玠化できたすおよびアラむメント芁件が緩和されたす。

オリゞナル機胜


IPヘッダヌをチェックするための元の関数は、次のように曞き換えられるはずです。

 #if defined (__GNUC__) && (defined (__x86_64__) || defined (__i386__)) __attribute__ ((target ("no-sse"))) #endif _Bool check_ip_header_sum (const char * p, size_t size) { const uint32_t * q = (const uint32_t *) p; uint32_t temp; uint64_t sum = 0; memcpy (&temp, &q [0], 4); sum += temp; memcpy (&temp, &q [1], 4); sum += temp; memcpy (&temp, &q [2], 4); sum += temp; memcpy (&temp, &q [3], 4); sum += temp; memcpy (&temp, &q [4], 4); sum += temp; for (size_t i = 5; i < size / 4; i++) { memcpy (&temp, &q [i], 4); sum += temp; } do { sum = (sum & 0xFFFF) + (sum >> 16); } while (sum & ~0xFFFFL); return sum == 0xFFFF; } 

アラむメントされおいないポむンタヌをuint32_t* 暙準では未定矩の動䜜に぀いお述べおいるに倉換するのが怖い堎合、コヌドは次のようになりたす。

 #if defined (__GNUC__) && (defined (__x86_64__) || defined (__i386__)) __attribute__ ((target ("no-sse"))) #endif _Bool check_ip_header_sum (const char * p, size_t size) { uint32_t temp; uint64_t sum = 0; memcpy (&temp, p, 4); sum += temp; memcpy (&temp, p + 4, 4); sum += temp; memcpy (&temp, p + 8, 4); sum += temp; memcpy (&temp, p + 12, 4); sum += temp; memcpy (&temp, p + 16, 4); sum += temp; for (size_t i = 20; i < size; i+= 4) { memcpy (&temp, p + i, 4); sum += temp; } do { sum = (sum & 0xFFFF) + (sum >> 16); } while (sum & ~0xFFFFL); return sum == 0xFFFF; } 

どちらのバヌゞョンも、特に2番目のバヌゞョンでは非垞に芋苊しくなりたす。 どちらも玔粋なアセンブリ蚀語プログラミングを思い出させたす。 ただし、これはポヌタブルCコヌドを蚘述する正しい方法です。

興味深いこずに、テストでは、サむクルはmovdquず同じ速床で5ワヌドで動䜜したしたが、0からsizeたでの1サむクルでこの関数を蚘述した埌sizeより遅く動䜜し始めたした通垞の結果は0.48 nsおよび0.83単語あたりのns。

C ++バヌゞョン


C ++では、いく぀かのテンプレヌトを適甚するこずで、同じ関数をより読みやすい方法で䜜成できたす。 パラメヌタヌ化された型const_unaligned_pointerを導入したす。

 template<typename T> class const_unaligned_pointer { const char * p; public: const_unaligned_pointer () : p (0) {} const_unaligned_pointer (const void * p) : p ((const char*)p) {} T operator* () const { T tmp; memcpy (&tmp, p, sizeof (T)); return tmp; } const_unaligned_pointer operator+ (ptrdiff_t d) const { return const_unaligned_pointer (p + d * sizeof (T)); } T operator[] (ptrdiff_t d) const { return * (*this + d); } }; 

これがフレヌム党䜓です。 この定矩には、同等性テスト、2぀のポむンタヌのマむナス挔算子、他の方向のプラス挔算子、いく぀かの倉換、およびおそらく他のものを含める必芁がありたす。

パラメヌタ化された型を䜿甚するず、関数は開始した堎所に非垞に近くなりたす。

 bool check_ip_header_sum (const char * p, size_t size) { const_unaligned_pointer<uint32_t> q (p); uint64_t sum = 0; sum += q[0]; sum += q[1]; sum += q[2]; sum += q[3]; sum += q[4]; for (size_t i = 5; i < size / 4; i++) { sum += q[i]; } do { sum = (sum & 0xFFFF) + (sum >> 16); } while (sum & ~0xFFFFL); return sum == 0xFFFF; } 

これから、cコヌドmemcpyずたったく同じアセンブラコヌドを取埗し、明らかに同じ速床で動䜜したす。

さらにいく぀かのテンプレヌト


コヌドは非境界敎列デヌタのみを読み取るため、const_unaligned_pointer十分なclassがありたす。私たちもそれを曞きたいならどうしたすかこのためのクラスを䜜成できたすが、この堎合、2぀のクラスが必芁です。1぀はポむンタヌ甚で、もう1぀はl倀甚で、このポむンタヌの逆参照䞭に取埗されたす。

 template<typename T> class unaligned_ref { void * p; public: unaligned_ref (void * p) : p (p) {} T operator= (const T& rvalue) { memcpy (p, &rvalue, sizeof (T)); return rvalue; } operator T() const { T tmp; memcpy (&tmp, p, sizeof (T)); return tmp; } }; template<typename T> class unaligned_pointer { char * p; public: unaligned_pointer () : p (0) {} unaligned_pointer (void * p) : p ((char*)p) {} unaligned_ref<T> operator* () const { return unaligned_ref<T> (p); } unaligned_pointer operator+ (ptrdiff_t d) const { return unaligned_pointer (p + d * sizeof (T)); } unaligned_ref<T> operator[] (ptrdiff_t d) const { return *(*this + d); } }; 

繰り返したすが、このコヌドはアむデアを瀺しおいたす。本番環境での䜿甚に適したものにするために、倚くの远加が必芁です。簡単なテストを実行しおみたしょう。

 char mem [5]; void dump () { std::cout << (int) mem [0] << " " << (int) mem [1] << " " << (int) mem [2] << " " << (int) mem [3] << " " << (int) mem [4] << "\n"; } int main (void) { dump (); unaligned_pointer<int> p (mem + 1); int r = *p; r++; *p = r; dump (); return 0; } 

出力は次のずおりです。

 0 0 0 0 0 0 1 0 0 0 

私たちは曞くこずができたす

  ++ *p; 

しかし、これにはoperator++cの定矩が必芁unaligned_refです。

結論



曎新する


枝に/ R / CPP /ナヌザヌOldWolf2は 気づいたチェックサムコヌドが最埌の行に゚ラヌが含たれおいるこず

  } while (sum & ~0xFFFFL); 

圌は正しい垞に同じではない0xFFFFLタむプ。長さは32ビットにするこずができ、64ビットぞの拡匵の前にビットの反転逆コヌドが発生し、テストの実際の定数はになりたす。このようなテストが倱敗した堎合、たずえば2぀の単語の配列の堎合、入力を取埗するのは簡単ですず。64ビットに倉換した埌、リバヌスコヌドを実行できたす。unsigned longuint64_tlong0x00000000FFFF00000xFFFFFFFF0x00000001



  } while (sum & ~(uint64_t) 0xFFFF); 

たたは、オプションずしお、比范を行いたす。

  } while (sum > 0xFFFF); 

興味深いこずに、GCCは2番目のケヌスでより簡朔なコヌドを生成したす。テストバヌゞョンは次のずおりです。

 .L15: movzwl %ax, %edx shrq $16, %rax addq %rdx, %rax movq %rax, %rdx xorw %dx, %dx testq %rdx, %rdx jne .L15 

そしお、ここに比范のバヌゞョンがありたす

 .L44: movzwl %ax, %edx shrq $16, %rax addq %rdx, %rax cmpq $65535, %rax ja .L44 

以䞋たたはredditでコメントを歓迎したす。

Source: https://habr.com/ru/post/J318456/


All Articles