web-dev-qa-db-ja.com

Cでポインターを位置合わせする方法

Cでポインターを位置合わせする方法はありますか?配列スタックにデータを書き込んでいて(ポインタが下に移動する)、次に書き込むデータを4に揃えて、データが4の倍数のメモリ位置に書き込まれるようにするとします。それ?

私が持っています

 uint8_t ary[1024];
 ary = ary+1024;
 ary -= /* ... */

ここで、aryが場所0x05を指しているとします。 0x04を指すようにします。今私はただすることができます

ary -= (ary % 4);

しかし、Cはポインターのモジュロを許可しません。アーキテクチャに依存しないソリューションはありますか?

28
Mark

配列は[〜#〜]ではありません[〜#〜]ポインタですが、ここで誤った回答を読んだ可能性があります(特にこの質問またはスタックオーバーフロー全般-または他の場所)。

以下に示すように、配列の名前で表される値を変更することはできません。

おそらく混乱しているのは、aryが関数パラメーターの場合、配列を調整できるように見えることです。

void function(uint8_t ary[1024])
{
    ary += 213; // No problem because ary is a uint8_t pointer, not an array
    ...
}

関数のパラメーターとしての配列は、関数の外部または関数の内部で定義された配列とは異なります。

できるよ:

uint8_t    ary[1024];
uint8_t   *stack = ary + 510;
uintptr_t  addr  = (uintptr_t)stack;

if (addr % 8 != 0)
    addr += 8 - addr % 8;
stack = (uint8_t *)addr;

これにより、stackの値が切り上げられた8バイトの境界に揃えられます。あなたの質問は、4バイト境界に切り捨てることを求めているため、コードは次のように変更されます。

if (addr % 4 != 0)
    addr -= addr % 4;
stack = (uint8_t *)addr;

はい、ビットマスクを使用してそれを行うことができます。どちらか:

addr = (addr + (8 - 1)) & -8;  // Round up to 8-byte boundary

または:

addr &= -4;                    // Round down to a 4-byte boundary

これは、LHSが2の累乗である場合にのみ正しく機能します。任意の値ではありません。モジュラス演算を含むコードは、どの(正の)モジュラスでも正しく機能します。

参照:標準ライブラリのみを使用して整列メモリを割り当てる方法


デモコード

Gnzlbgコメント

私が整列しようとすると、2の累乗のコードが中断します。 1バイト境界までのuintptr_t(2)(どちらも2の累乗:2 ^ 1および2 ^ 0)。結果は1ですが、2はすでに1バイト境界に位置合わせされているため、2になるはずです。

このコードは、上記のコメントを正しく解釈している限り、配置コードが問題ないことを示しています(ビットマスキング操作を区切る「どちらかまたは」の単語によって明確になりました。コードを最初にチェックしたときに引っ掛かりました)。

整列関数は、特にアサーションなしで、よりコンパクトに記述できますが、コンパイラーは、記述されたものと記述可能なものから同じコードを生成するように最適化します。一部のアサーションもより厳しくすることができます。そして、おそらくテスト関数は他のことをする前にスタックのベースアドレスを出力するべきです。

コードは、算術演算で数値のオーバーフローまたはアンダーフローが発生しないことを確認できます。これは、アドレスをマルチメガバイト境界に揃えた場合に問題になる可能性が高くなります。 1KiB未満の配置を維持している間、アクセスできる配列の境界から出ようとしない限り、問題を見つけることはほとんどありません。 (厳密に言えば、数メガバイトのアライメントを行ったとしても、結果が操作している配列に割り当てられたメモリの範囲内であれば問題は発生しません。)

#include <assert.h>
#include <stdint.h>
#include <stdio.h>

/*
** Because the test code works with pointers to functions, the inline
** function qualifier is moot.  In 'real' code using the functions, the
** inline might be useful.
*/

/* Align upwards - arithmetic mode (hence _a) */
static inline uint8_t *align_upwards_a(uint8_t *stack, uintptr_t align)
{
    assert(align > 0 && (align & (align - 1)) == 0); /* Power of 2 */
    assert(stack != 0);

    uintptr_t addr  = (uintptr_t)stack;
    if (addr % align != 0)
        addr += align - addr % align;
    assert(addr >= (uintptr_t)stack);
    return (uint8_t *)addr;
}

/* Align upwards - bit mask mode (hence _b) */
static inline uint8_t *align_upwards_b(uint8_t *stack, uintptr_t align)
{
    assert(align > 0 && (align & (align - 1)) == 0); /* Power of 2 */
    assert(stack != 0);

    uintptr_t addr  = (uintptr_t)stack;
    addr = (addr + (align - 1)) & -align;   // Round up to align-byte boundary
    assert(addr >= (uintptr_t)stack);
    return (uint8_t *)addr;
}

/* Align downwards - arithmetic mode (hence _a) */
static inline uint8_t *align_downwards_a(uint8_t *stack, uintptr_t align)
{
    assert(align > 0 && (align & (align - 1)) == 0); /* Power of 2 */
    assert(stack != 0);

    uintptr_t addr  = (uintptr_t)stack;
    addr -= addr % align;
    assert(addr <= (uintptr_t)stack);
    return (uint8_t *)addr;
}

/* Align downwards - bit mask mode (hence _b) */
static inline uint8_t *align_downwards_b(uint8_t *stack, uintptr_t align)
{
    assert(align > 0 && (align & (align - 1)) == 0); /* Power of 2 */
    assert(stack != 0);

    uintptr_t addr  = (uintptr_t)stack;
    addr &= -align;                         // Round down to align-byte boundary
    assert(addr <= (uintptr_t)stack);
    return (uint8_t *)addr;
}

static inline int inc_mod(int x, int n)
{
    assert(x >= 0 && x < n);
    if (++x >= n)
        x = 0;
    return x;
}

typedef uint8_t *(*Aligner)(uint8_t *addr, uintptr_t align);

static void test_aligners(const char *tag, Aligner align_a, Aligner align_b)
{
    const int align[] = { 64, 32, 16, 8, 4, 2, 1 };
    enum { NUM_ALIGN = sizeof(align) / sizeof(align[0]) };
    uint8_t stack[1024];
    uint8_t *sp = stack + sizeof(stack);
    int dec = 1;
    int a_idx = 0;

    printf("%s\n", tag);
    while (sp > stack)
    {
        sp -= dec++;
        uint8_t *sp_a = (*align_a)(sp, align[a_idx]);
        uint8_t *sp_b = (*align_b)(sp, align[a_idx]);
        printf("old %p, adj %.2d, A %p, B %p\n",
               (void *)sp, align[a_idx], (void *)sp_a, (void *)sp_b);
        assert(sp_a == sp_b);
        sp = sp_a;
        a_idx = inc_mod(a_idx, NUM_ALIGN);
    }
    putchar('\n');
}

int main(void)
{
    test_aligners("Align upwards", align_upwards_a, align_upwards_b);
    test_aligners("Align downwards", align_downwards_a, align_downwards_b);
    return 0;
}

出力例(一部省略):

Align upwards
old 0x7fff5ebcf4af, adj 64, A 0x7fff5ebcf4c0, B 0x7fff5ebcf4c0
old 0x7fff5ebcf4be, adj 32, A 0x7fff5ebcf4c0, B 0x7fff5ebcf4c0
old 0x7fff5ebcf4bd, adj 16, A 0x7fff5ebcf4c0, B 0x7fff5ebcf4c0
old 0x7fff5ebcf4bc, adj 08, A 0x7fff5ebcf4c0, B 0x7fff5ebcf4c0
old 0x7fff5ebcf4bb, adj 04, A 0x7fff5ebcf4bc, B 0x7fff5ebcf4bc
old 0x7fff5ebcf4b6, adj 02, A 0x7fff5ebcf4b6, B 0x7fff5ebcf4b6
old 0x7fff5ebcf4af, adj 01, A 0x7fff5ebcf4af, B 0x7fff5ebcf4af
old 0x7fff5ebcf4a7, adj 64, A 0x7fff5ebcf4c0, B 0x7fff5ebcf4c0
old 0x7fff5ebcf4b7, adj 32, A 0x7fff5ebcf4c0, B 0x7fff5ebcf4c0
old 0x7fff5ebcf4b6, adj 16, A 0x7fff5ebcf4c0, B 0x7fff5ebcf4c0
old 0x7fff5ebcf4b5, adj 08, A 0x7fff5ebcf4b8, B 0x7fff5ebcf4b8
old 0x7fff5ebcf4ac, adj 04, A 0x7fff5ebcf4ac, B 0x7fff5ebcf4ac
old 0x7fff5ebcf49f, adj 02, A 0x7fff5ebcf4a0, B 0x7fff5ebcf4a0
old 0x7fff5ebcf492, adj 01, A 0x7fff5ebcf492, B 0x7fff5ebcf492
…
old 0x7fff5ebcf0fb, adj 08, A 0x7fff5ebcf100, B 0x7fff5ebcf100
old 0x7fff5ebcf0ca, adj 04, A 0x7fff5ebcf0cc, B 0x7fff5ebcf0cc
old 0x7fff5ebcf095, adj 02, A 0x7fff5ebcf096, B 0x7fff5ebcf096

Align downwards
old 0x7fff5ebcf4af, adj 64, A 0x7fff5ebcf480, B 0x7fff5ebcf480
old 0x7fff5ebcf47e, adj 32, A 0x7fff5ebcf460, B 0x7fff5ebcf460
old 0x7fff5ebcf45d, adj 16, A 0x7fff5ebcf450, B 0x7fff5ebcf450
old 0x7fff5ebcf44c, adj 08, A 0x7fff5ebcf448, B 0x7fff5ebcf448
old 0x7fff5ebcf443, adj 04, A 0x7fff5ebcf440, B 0x7fff5ebcf440
old 0x7fff5ebcf43a, adj 02, A 0x7fff5ebcf43a, B 0x7fff5ebcf43a
old 0x7fff5ebcf433, adj 01, A 0x7fff5ebcf433, B 0x7fff5ebcf433
old 0x7fff5ebcf42b, adj 64, A 0x7fff5ebcf400, B 0x7fff5ebcf400
old 0x7fff5ebcf3f7, adj 32, A 0x7fff5ebcf3e0, B 0x7fff5ebcf3e0
old 0x7fff5ebcf3d6, adj 16, A 0x7fff5ebcf3d0, B 0x7fff5ebcf3d0
old 0x7fff5ebcf3c5, adj 08, A 0x7fff5ebcf3c0, B 0x7fff5ebcf3c0
old 0x7fff5ebcf3b4, adj 04, A 0x7fff5ebcf3b4, B 0x7fff5ebcf3b4
old 0x7fff5ebcf3a7, adj 02, A 0x7fff5ebcf3a6, B 0x7fff5ebcf3a6
old 0x7fff5ebcf398, adj 01, A 0x7fff5ebcf398, B 0x7fff5ebcf398
…
old 0x7fff5ebcf0f7, adj 01, A 0x7fff5ebcf0f7, B 0x7fff5ebcf0f7
old 0x7fff5ebcf0d3, adj 64, A 0x7fff5ebcf0c0, B 0x7fff5ebcf0c0
old 0x7fff5ebcf09b, adj 32, A 0x7fff5ebcf080, B 0x7fff5ebcf080
43

モジュールを使用しないでください!!! IT IS REALLY SLOW !!!ポインタを揃える最速の方法は、2の補数演算を使用することです。ビットを反転し、1を追加し、2をマスクする必要があります(32 -bit)または3(64ビットの場合)の最下位ビット。結果はオフセットになり、それを位置合わせするためにポインター値に追加されます。32ビットおよび64ビットの数値に最適です。16ビットの位置合わせの場合は、ポインタは0x1で、その値を追加します。アルゴリズムはどの言語でも同じように機能しますが、ご覧のとおり、埋め込みC++は、あらゆる形や形でCよりもはるかに優れています。

#include <cstdint>
/** Returns the number to add to align the given pointer to a 8, 16, 32, or 64-bit 
    boundary.
    @author Cale McCollough.
    @param  ptr The address to align.
    @return The offset to add to the ptr to align it. */
template<typename T>
inline uintptr_t MemoryAlignOffset (const void* ptr) {
    return ((~reinterpret_cast<uintptr_t> (ptr)) + 1) & (sizeof (T) - 1);
}

/** Word aligns the given byte pointer up in addresses.
    @author Cale McCollough.
    @param ptr Pointer to align.
    @return Next Word aligned up pointer. */
template<typename T>
inline T* MemoryAlign (T* ptr) {
    uintptr_t offset = MemoryAlignOffset<uintptr_t> (ptr);
    char* aligned_ptr = reinterpret_cast<char*> (ptr) + offset;
    return reinterpret_cast<T*> (aligned_ptr);
}

詳細な記述と証明については、@ see https://github.com/kabuki-starship/kabuki-toolkit/wiki/Fastest-Method-to-Align-Pointers を参照してください。モジュロを使用してはならない理由の証明を知りたい場合は、世界最速の整数から文字列へのアルゴリズムを考案しました。紙のベンチマークは、1つのモジュロ命令のみを最適化する効果を示しています。 @see https://github.com/kabuki-starship/kabuki-toolkit/wiki/Engineering-a-Faster-Integer-to-String-Algorithm を参照してください。

Graph of why you shouldn't use modulo

2
user2356685

他の場所で学んだトリックに基づいて、@ parの回答を読んだことによる1つは、32ビットのマシンのような特別な場合に必要なのは((size - 1) | 3) + 1このように動作し、他の人に役立つかもしれないと考えました、

for (size_t size = 0; size < 20; ++size) printf("%d\n", ((size - 1) | 3) + 1);

0
4
4
4
4
8
8
8
8
12
12
12
12
16
16
16
16
20
20
20
1
Ebrahim Byagowi

私はこの答えを編集しています:

  1. 元のコードにバグがありました(intptr_tへの型キャストを忘れました)。
  2. 私の意図を明確にするために、ジョナサン・レフラーの批判に答えています。

以下のコードは、配列(foo)の値を変更できることを意味するものではありません。しかし、あなたはcanがその配列に整列されたポインタを取得し、この例はそれを行う1つの方法を示しています。

#define         alignmentBytes              ( 1 << 2 )   // == 4, but enforces the idea that that alignmentBytes should be a power of two
#define         alignmentBytesMinusOne      ( alignmentBytes - 1 )

uint8_t         foo[ 1024 + alignmentBytesMinusOne ];
uint8_t         *fooAligned;

fooAligned = (uint8_t *)((intptr_t)( foo + alignmentBytesMinusOne ) & ~alignmentBytesMinusOne);
1
par

なんらかの理由で、モジュロ演算やビット演算を使用できません。この場合:

void *alignAddress = (void*)((((intptr_t)address + align - 1) / align) * align) ;

C++の場合:

template <int align, typename T>
constexpr T padding(T value)
{
    return ((value + align - 1) / align) * align;
}
...
char* alignAddress = reinterpret_cast<char*>(padding<8>(reinterpret_cast<uintptr_t>(address)))
0
mr NAE