1

I am working on some C code on micro processor stm32f103. Since allocating memory from the heap isn't stable, I am not encouraged to use the C library functions malloc() and free() etc. Instead, I thought of declaring a large chunk of static memory in advance during compilation time, and reallocating the memory to suit my pseudo dynamic memory allocation purposes. My new malloc implementation works fine when testing on my computer, but crashes on the stm32 when I do malloc for double data type.

Here is my malloc implementation. I know it is not a real dynamic memory allocation, but i do it just to practice using pointers.

pk_malloc.c

#include "pk_malloc.h"

char pool[RESERVE];
void* alloc[RESERVE];

void mem_init()
{
    for (int i = 0; i != RESERVE; i++)
    {
        alloc[i] = NULL;
    }
}

void* mem_malloc(size_t size)
{
    if (size > 0)
    {
        for (int i = 0; i != RESERVE; i++)
        {
            if (alloc[i] == NULL)
            {
                int end;
                for (end = i; end != RESERVE; end++)
                {
                    if (alloc[end] != NULL || end - i == size + 1)
                    {
                        break;
                    }
                }
                if (end - i == size + 1)
                {
                    for (int k = i + 1; k != end; k++)
                    {
                        alloc[k] = &pool[k];
                    }
                    return alloc[i + 1];
                }
            }
        }
    }
    return NULL;
}

void* mem_realloc(void* mem, size_t new_size)
{
    if (mem == NULL)
    {
        return mem_malloc(new_size);
    }
    int old_size = 0;
    void** alloc_t = &alloc[(char*)(mem) - pool];
    while (*alloc_t != NULL)
    {
        old_size++;
        alloc_t++;
    }
    if (new_size <= old_size)
    {
        mem_free((char*)mem + new_size);
        return mem;
    }
    else
    {
        int i = alloc_t - alloc;
        int size = new_size - old_size;
        int end;
        for (end = i; end != RESERVE; end++)
        {
            if (alloc[end] != NULL || end - i == size + 1)
            {
                break;
            }
        }
        if (end - i == size + 1)
        {
            for (int k = i; k != end - 1; k++)
            {
                alloc[k] = &pool[k];
            }
            return alloc[i];
        }
        else
        {
            void* realloc_t = mem_malloc(new_size);
            if (realloc_t == NULL)
            {
                return mem;
            }
            else
            {
                mem_copy(realloc_t, mem);
                mem_free(mem);
                return realloc_t;
            }
        }
    }
}

void mem_copy(void* dest, void* source)
{
    int dest_index = (char*)(dest) - pool;
    int source_index = (char*)(source) - pool;
    char* writer = (char*)(source);
    while (alloc[source_index] != NULL && alloc[dest_index] != NULL)
    {
        pool[dest_index] = pool[source_index];
        dest_index++;
        source_index++;
    }
}

void mem_free(void* mem)
{
    if (mem != NULL)
    {
        void** alloc_t = &alloc[(char*)(mem) - pool];
        while (*alloc_t != NULL)
        {
            *alloc_t = NULL;
            alloc_t++;
        }
    }
}

pk_malloc.h

#ifndef _PK_MALLOC
#define _PK_MALLOC

#include <stdlib.h>

#define RESERVE 64

void mem_init();

void* mem_malloc(size_t size);
void* mem_realloc(void* mem, size_t new_size);

void mem_copy(void* dest, void* source);

void mem_free(void* mem);

#endif

main.c

int main()
{
    mem_init();
    int* hoho = (int*)(mem_malloc(sizeof(int)));
    *hoho = 123;
    printf("%d", *hoho);
    mem_free(hoho);
}

The code works on my computer, and also works on the STM32. However when I change my datatype to a double:

int main()
{
    mem_init();
    double* hoho = (double*)(mem_malloc(sizeof(double)));
    *hoho = 0.618;
    printf("%f", *hoho);
    mem_free(hoho);
}

It only works on my computer, while it crashed on the STM32.

I did some testing and debugs, I find that this line works, the pointer is not NULL and it has a valid address.

double* hoho = (double*)(mem_malloc(sizeof(double)));

However this line crashed.

*hoho = 0.618;

After more testing, I find any data types occupying more than 4 bytes crashes the same way, including long long etc.

Strangely, I made some user defined structs with lots of int, float data types etc, which definitely occupies more than 4 bytes, the code works fine on the STM32.

struct ABC
{
    int a;
    float b;
};

This line works no problems.

struct ABC* hoho = (struct ABC*)(mem_malloc(sizeof(struct ABC)));

The variable hoho can be assigned, and its members can be accessed without ease, working both on my computer and the STM32.

Please note that all the code works on my laptop, and most data types work for the STM32 too.

I have been stuck with the problem for hours, any help appreciated.

christopher_pk
  • 641
  • 4
  • 17
  • 5
    There might be alignment requirements for `double`s. – Ry- Mar 27 '17 at 16:45
  • alignment requirement? does it mean 8 bytes is not enough to store the double? – christopher_pk Mar 27 '17 at 16:47
  • 2
    Yeah, maybe you should try just hacking your code so that all addresses returned are 8-aligned. I would also try 4-aligned to see if that's sufficient. Also, try printing a statically-allocated double just to make sure that `printf` is not the thing causing a crash. – David Grayson Mar 27 '17 at 16:50
  • so starting from the first element of the char array I pre-allocated, I only malloc an address with its index satisfying i % 8 == 0? – christopher_pk Mar 27 '17 at 16:52
  • I tested, printf is not causing any issues. – christopher_pk Mar 27 '17 at 17:03
  • about the alignment issue, I don't quite understand. Why does it work for my other datatypes though? eg int, float, structs? – christopher_pk Mar 27 '17 at 17:08
  • 1
    The error message for the exception / crash could really help us to isolate the issue. Usually the CPU will throw and alignment exception or a SMMU error or something upon bad access. What is this telling you? Also, if this is alignment, just hack your first malloc to make sure that the pointer it returns has a '0' in the address for the last byte- this would be 16-byte aligned and should work for a test. Then use that as a double to check. As to why double and not others? Perhaps the double value is being used by a math co-processor or being shifted to registers by the compiler. – Michael Dorgan Mar 27 '17 at 17:23
  • Note that if you allocate only at 8-byte boundaries, then you can reduce the size of your `alloc` array by a factor of 8. Right now, `alloc` takes up much more space than does `pool`, so your overall allocation scheme is grossly inefficient with respect to memory use. – John Bollinger Mar 27 '17 at 17:23
  • Also, instead of reinventing this wheel, there are PLENTY of free allocators you could grab and use that support alignment out of the box. Just google memory allocators and enjoy. – Michael Dorgan Mar 27 '17 at 17:25
  • 2
    With respect to alignment, do note that the specifications for the standard `malloc()` and `calloc()` functions promise that "The pointer returned if the allocation succeeds is suitably aligned so that it may be assigned to a pointer to any type of object with a fundamental alignment requirement and then used to access such an object or an array of such objects in the space allocated" (C2011, 7.22.3/1). If your system indeed has alignment requirements larger than 1 for some data types, then your allocator, as presented, cannot make the same promise. – John Bollinger Mar 27 '17 at 17:29
  • http://stackoverflow.com/a/18269440/918959 – Antti Haapala -- Слава Україні Mar 28 '17 at 06:35
  • thanks all, understood. – christopher_pk Mar 28 '17 at 07:44

1 Answers1

1

The core in STM32F1 is a Cortex-M3. This QA here points out that while unaligned word access is allowed by Cortex-M3 for simple instructions, not all instructions support unaligned access. In your case, the C compiler uses an instruction that doesn't support an unaligned address with double.

Notice that

  • the standard library malloc returns a pointer that is "suitably aligned so that it may be assigned to a pointer to any type of object with a fundamental alignment requirement and then used to access such an object or an array of such objects in the space allocated" (C11 7.22.3)

  • while a pointer may be converted to another pointer, "if the resulting pointer is not correctly aligned for the referenced type, the behavior is undefined" (C11 6.3.2.3p7).

Thus the behaviour of a program is undefined already at these lines

int *hoho = mem_malloc(sizeof(int));
double *hoho = mem_malloc(sizeof(double));

if the pointer returned is not suitably aligned for int and double respectively.

To fix the code, change it so that it always returns pointers of proper alignment. The official ARM compilers maintain an 8-byte aligned heap.

Community
  • 1
  • 1