Helper class for accessing blobs and mmap memory

Posted on

Problem

This class was inspired from Microsoft’s array_view.

It intended to use with mmap-ed memory for easily check bounds and so on.

I probably will need to throw exceptions, but this is not my usual way of working.

Any comments are welcome.

#include <cstring>

class BlobRef{
public:
    BlobRef(const void *mem, size_t const size) noexcept :
                mem_( (const char *) mem),
                size_(size){}

    // for test purposes
    BlobRef(const char *s) noexcept :
                BlobRef(s, strlen(s) + 1){}

    template<size_t N>
    BlobRef(const char(&mem)[N]) noexcept:
                BlobRef(mem, N){}

public:
    bool empty() const noexcept{
        return mem_ == nullptr || size_ == 0;
    }

    size_t size() const noexcept{
        return size_;
    }

    const void *data_() const noexcept{
        return mem_;
    }

public:
    const void *safeAccessMemory(size_t const pos, size_t const size) const noexcept{
        if (pos + size <= size_)
            return & mem_[pos];
        else
            return nullptr;
    }

    template <class T>
    const T *as(size_t const pos = 0, size_t const elements = 1) const noexcept{
        return (const T *) safeAccessMemory(pos, elements * sizeof(T));
    }

private:
    const char  *mem_ = nullptr;
    size_t      size_ = 0;
};


#include <cstdint>
#include <assert.h>
#include <endian.h>

int test_blobref(){
    constexpr size_t SIZE = 256;
    char mem[SIZE];

    for(int i = 0; i < SIZE; ++i)
        mem[i] = (char) i;

    BlobRef br{ mem };

    assert(*br.as<uint16_t>(0x00) == htobe16(0x0001)    );
    assert(*br.as<uint16_t>(0x0E) == htobe16(0x0E0F)    );
    assert(*br.as<uint32_t>(0x10) == htobe32(0x10111213)    );

    {
        const char *s = br.as<char>('a');
        assert(strncmp(s, "abcde", 5) == 0);
    }

    {
        struct TestStruct{
            uint16_t    i;
            char        c;
            char        s[4];
        }__attribute__((__packed__));

        const TestStruct *st = br.as<TestStruct>(0x50);
        assert(st->i == htobe16(0x5051)     );
        assert(st->c == 0x52            );

        const char *s1 = st->s;
        const char s2[] = { 0x53, 0x54, 0x55, 0x56, 0x57 };
        assert(strncmp(s1, s2, sizeof s2) == 0);
    }

    {
        size_t const pos = SIZE / sizeof(uint64_t);

        const uint64_t *u64 = br.as<uint64_t>(0, pos);
        assert(u64 != nullptr   );
        assert(u64[      0] == htobe64(0x0001020304050607)  );
        assert(u64[pos - 1] == htobe64(0xf8f9fafbFCFDFEFF)  );
    }

    {
        size_t const pos = SIZE / sizeof(uint64_t) ;

        const uint64_t *u64 = br.as<uint64_t>(0, pos + 1 );
        assert(u64 == nullptr   );
    }

    return 0;
}

int main(){
    test_blobref();
}

Solution

BlobRef details

  • You should provide a default constructor.
  • safeAccessMemory contains a bug. If you have an empty BlobRef and you call safeAccessMemory(0, 0) you will deference a nullptr (the guard fails).
  • Don’t use c-style casts. In your cases you should use reinterpret_cast. This helps readability, search-ability and signals clear intent.
  • as can invoke undefined behaviour if called on certain types. The gritty details are in 3.10.10 of the C++ spec.

If a program attempts to access the stored value of an object through
a glvalue of other than one of the following types the behavior is
undefined:

the dynamic type of the object.

a cv-qualified version of the dynamic type of the object.

a type similar (as defined in 4.4) to the dynamic type of the object.

a type that is the signed or unsigned type corresponding to the dynamic type of the object.

a type that is the signed or unsigned type corresponding to a cv-qualified version of the dynamic type of the object.

an aggregate or union type that includes one of the aforementioned types among its elements or nonstatic data members (including, recursively, an element or non-static data member of a subaggregate or contained union), a type that is a (possibly cv-qualified) base class type of the dynamic type of the object.

a char or unsigned char type.

My understand is that there’s no easy way to make this work (the gsl folks removed theirs)

Unfortunately this would remove the entire essence of this class. One approach you could take to remedy this would be to use type-traits to restrict the function to types for which it will be well defined.

template <class T, typename = std::enable_if_t<std::is_integral<T>::value>>
const T *as(size_t const pos = 0, size_t const elements = 1) const noexcept{
    return (const T *) safeAccessMemory(pos, elements * sizeof(T));
}

Leave a Reply

Your email address will not be published. Required fields are marked *