Правильный способ выравнивания указателя - PullRequest
0 голосов
/ 23 июня 2018

У меня следующий код для выравнивания указателя

char *p = new char[1000];
//...
++p;
//...
int alignment = 4;
int extra_bytes = (size_t)p % alignment;

int *aligned_ptr = (int*)(p + (alignment - extra_bytes));
std::cout << ((size_t)aligned_ptr % alignment); //aligned
//... 

Я хочу знать, это правильный код для выравнивания указателя? Если да, может кто-нибудь здесь показать реализацию, которая лучше? (Например, используя побитовые операции)

Ответы [ 2 ]

0 голосов
/ 23 июня 2018

Как правило, вам на самом деле не нужно приводить указатель для проверки его битов, поскольку вы можете использовать индексы в буфере. Попробуйте использовать что-то вроде:

#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <stdexcept>
#include <vector>

// in case your compiler doesn't support C++11 `alignof`,
// it's easy to implement
#define ALIGNOF(T) (sizeof(alignof_helper<T>) - sizeof(T))
// or in GNU C, although it provides __alignof__ anyway
#define ALIGNOF_C(T) ({ struct alignof_c_helper { char c; T data; }; sizeof(alignof_c_helper) - sizeof(T); })
// but of course, macros are EVIL.
template<class T>
struct alignof_helper
{
    char c;
    // implicit padding since data must be aligned
    T data;
};

struct aligned_buffer
{
    std::vector<uint8_t> vec;
    size_t index;

    aligned_buffer(size_t sz = 0)
    : vec(sz)
    , index(0)
    {
    }

    template<class T>
    T *get(size_t count=1)
    {
        // malloc() and ::operator new() return normally-aligned memory
        static_assert(alignof(T) <= alignof(std::max_align_t), "no overaligned types without a special allocator");
        size_t offset = this->index % alignof(T);
        size_t start_index = this->index;
        size_t new_index, asize;
        if (offset)
        {
            start_index += alignof(T) - offset;
            if (!start_index) // overflowed
                throw std::length_error("how did you allocate that much? I'm impressed");
        }
        if (__builtin_mul_overflow(alignof(T), count, &asize) || __builtin_add_overflow(start_index, asize, &new_index))
        {
            throw std::length_error("ridiculous size");
        }
        if (new_index > this->vec.size())
        {
            throw std::length_error("insufficient reserved space");
        }
        this->index = new_index;
        return reinterpret_cast<T *>(&this->vec[start_index]);
    }
};

int main()
{
    static_assert(alignof(int) == ALIGNOF(int), "C++98 version");
    static_assert(alignof(int) == ALIGNOF_C(int), "GNU C statement-expression");
    static_assert(alignof(int) == __alignof__(int), "GNU C keyword");

    static_assert(alignof(int) == 4 && alignof(long long) == 8, "tests below assume a \"normal\" environment");
    aligned_buffer buf(16);
    *buf.get<char>() = 'A';
    auto a = buf.get<int>(2);
    a[0] = 123;
    a[1] = 456;
    try
    {
        buf.get<long long>();
        throw std::logic_error("code is wrong I guess?");
    }
    catch (std::length_error& e)
    {
    }
    // can still use the buffer
    *buf.get<char>() = 'Z';

    puts("everything is okay");
}

Изменение этого кода для безопасного изменения размера нижележащего буфера и, таким образом, беспокойство о недействительности указателя, оставлено в качестве упражнения для читателя.

0 голосов
/ 23 июня 2018

Указатель выравнивается, если ваш коэффициент равен 0 после выполнения операции по модулю Выравнивание мод указателя .

Это означает, что вы можете проверить (в C ++ 11), используя

#include <cstdint>
bool isAligned = ((reinterpret_cast<std::uintptr_t>(pointer) % alignment) == 0);

Вы также можете сделать это, используя побитовую операцию (только если выравнивание имеет степень 2)

bool isAligned = ((reinterpret_cast<std::uintptr_t>(pointer) & (alignment - 1)) == 0);

Однако компилятор, вероятно, оптимизирует это для вас.

...