Dynamic allocation a no-no on Teensy/Arduino?

ariesboy571

Well-known member
I have come across an odd problem, and I'm not sure what's going on.
If I load and run this code on a Teensy 4.1:

C++:
int8_t* testMe(uint8_t* myList) {
    //uint8_t* numList = new uint8_t[sizeof(myList)];
      uint8_t* numList = (uint8_t*)malloc(sizeof(myList)); // effectively the same as the above statement
      memcpy(numList, myList, sizeof(myList));
    return numList;
}

void setup() {

    Serial.begin(19200);
    while (!Serial && millis() < 5000) {
        // Wait for Serial
    }
    Serial.print("Starting...\r\n");

    uint8_t mp[8] = {50, 52, 54, 56, 58, 60, 62, 64};

    Serial.print("The number list A: ");
    for(int a=0; a<8; a++) {
        int xxx = static_cast<int>(mp[a]);
        Serial.print(xxx);
        Serial.print(", ");
    }

    Serial.println();
    Serial.print("The number list B: ");

    uint8_t* myTest = testMe(mp);

    for(int a=0; a<8; a++) {
        int yyy = static_cast<int>(myTest[a]);
        Serial.print(yyy);
        Serial.print(", ");
    }
    Serial.println();

    free(myTest);
}

void loop() {
  // not today, Satan...
}

I get this response:

Code:
00:02:03.036 -> Starting...
00:02:03.036 -> The number list A: 50, 52, 54, 56, 58, 60, 62, 64,
00:02:03.036 -> The number list B: 50, 52, 54, 56, 139, 0, 207, 205,

The last four numbers in list B remain (generally) the same; they change if I power-cycle the Teensy.
On a stock Arduino Uno, the code generates nearly the same output:

Code:
23:52:25.301 -> Starting...
23:52:26.246 -> The number list A: 97, 98, 99, 3, 4, 5, 6, 9,
23:52:26.278 -> The number list B: 97, 98, 2, 0, 0, 1, 163, 89,

Except that I'm now reduced to two useful digits.

The same code, modified for Linux, and run on Ubuntu 24:


C++:
#include <stdio.h>
#include <cstring>
#include <cstdlib>

unsigned char* testMe(unsigned char* myList) {
    //unsigned char* numList = new unsigned char[sizeof(myList)];
    unsigned char* numList = (unsigned char*)malloc(sizeof(myList));
    memcpy(numList, myList, sizeof(myList));
    return numList;
}

int main() {
    // put your setup code here, to run once:
    unsigned char mp[8] = {0, 1, 2, 3, 4, 5, 6, 9};

    printf("Number List A: ");

    for(int a=0; a<8; a++) {
        printf("%x ", mp[a]);
    }
    printf("\r\n");

    unsigned char* myTest = testMe(mp);

    printf("Number List B: ");
    for(int a=0; a<8; a++) {
        printf("%x ", myTest[a]);
    }

    return 0;
}

Running on the command line (g++) generates the output I'd expect:

Code:
Number List A: 0 1 2 3 4 5 6 9
Number List B: 0 1 2 3 4 5 6 9

What on earth am I doing wrong?
 
Code:
int8_t* testMe(uint8_t* myList) {
    //uint8_t* numList = new uint8_t[sizeof(myList)];
      uint8_t* numList = (uint8_t*)malloc(sizeof(myList)); // effectively the same as the above statement
      memcpy(numList, myList, sizeof(myList));
    return numList;
}
This code doesn't do what you're intending it to.

myList is a pointer. sizeof(myList) gives the size of a pointer, not the size of the array passed in as an argument to the function. On a Teensy 4.x the size of a pointer is 4 bytes. On an Arduino it will be 2 or 1 byte(s). On a typical desktop x86_64 PC the size of a pointer will be 8 bytes.
 
To answer your question - dynamic memory allocation is fine.
It's generally avoided whenever possible for embedded systems except on start-up but this is more for system speed and stability reasons rather than because it doesn't work.

As has already been pointed out the reason you hit issues is due to a software bug / misunderstanding that is incredibly common when people are learning c / c++.
This is why virtually any c function that takes an array as a parameter also takes a second argument with the length of the array. About the only exception is when passing a char* containing a text string, then it's sometimes assumed that the string is null terminated and so the length can be easily found.
 
Maybe it was not clearly explained but you need to pass a lenght variable to the function
like this:
C++:
unsigned char* testMe(unsigned char* myList, size_t length) {
    //unsigned char* numList = new unsigned char[length];
    unsigned char* numList = (unsigned char*)malloc(length);
    memcpy(numList, myList, sizeof(myList));
    return numList;
}
and then call it like this:
C++:
uint8_t mp[8] = {50, 52, 54, 56, 58, 60, 62, 64};
// sizeof is a compile-time operator that returns the size
// of objects the compiler can fully determine during compilation.
// It does not execute at runtime and has no performance cost.
// You can use std::size(mp) in C++17 or sizeof(mp)/sizeof(mp[0]) in older C++.

int8_t* myTest = testMe(mp, sizeof(mp)/sizeof(mp[0]));

// do some stuff with myTest here

Also remember to free/delete the allocated memory when you are done using it to avoid memory leaks

C++:
free(myTest); //if you where using: unsigned char* numList = (unsigned char*)malloc(length);
delete[] myTest; // if you where using: uint8_t* numList = new uint8_t[length];

don't mix free and delete functions

a sidenote how both using new and using malloc work under the hood:
they both store some metadata before the actual pointer that is returned
so its in theory possible to retreive the size but there are no api:s in the standard c/c++ library

so you need to manage the size yourself in code (i.e returning/passing the size of allocated memory) to/from functions

because of this metadata is why you can actually use the free/delete functions outside the function that did allocate the memory

the following is a primitive example how you can create a array in a function and return both the data pointer and the size at the 'same' time:
C++:
struct ArrayResult {
    int* data;
    size_t size;
    // destructor to automatically cleanup
    ~ArrayResult() {
        delete[] data;
    }
    // block copy functions, to avoid double frees
    ArrayResult(const ArrayResult&) = delete;
    ArrayResult& operator=(const ArrayResult&) = delete;
};

ArrayResult createArray() {
    size_t length = 10;
    int* arr = new int[length];
    // Initialize arr if needed
    return {arr, length};
}
// Usage
int main() {
    ArrayResult result = createArray();
    // use result.data and result.size

    // No need to manually delete, destructor frees memory
}
it's a crude example how to return many objects from a function

some extra sidenotes:

you cannot do the following:
C++:
int* doStuff() {
    int numList[8];
    return numList;  // or return &numList[0];
}
as the array/object is cleared/destroyed when the function ends and the pointer returned is invalid

you cannot do the following either:
C++:
int* doStuff(size_t length) {
    int numList[length]; // this is invalid
    return numList;  // will not come here
}

I have learned a lot c/c++ by asking chatgpt (it's my prefered way but also copilot sometimes),
but a warning they can sometimes make up stuff, specially if it's unknown to them
and sometimes they also make up uses of libraries that do not exist or use outdated information
so lockup and double-check information

sometimes i have asked it for some code and when im posting the near same code back it points out
many fixes to make the code better even if it was itself that was creating it from the beginning



edit. i forgot to say that when you use the sizeof function like that it only returns the size of the pointer that is 4 bytes while using 32bit systems and would return 8bytes in 64bit systems (could also depend on other factors) but thats why you only get the first 4 bytes correct in the second print and the other 4 bytes are random data because the function will not set those to known values.
 
Last edited:
You know, I would not have figured that. Thank you, Andy, JMarsh, and Manicksan, for your explanations! I did some follow-up work, and sure enough, that was it. The "testMe()" function works, when you actually supply the *real* length of the data to it.
I've been avoiding pointers, for years. It seemed like witchcraft, a Murky Gray Area. Except that I'm going to have to use them, to get this project to work, so I'm diving in. And yes, both Claude and Gemini seem to hallucinate on pointer-based code; Claude somewhat less so, but they're both perfectly happy to feed you something that superficially appears to work. I just haven't developed the facility with pointers that most C++ developers have, as of yet, so I'm easy to beguile...

If these LLMs were developed more or less "for entertainment purposes only", and I try to use code generated by one or more of them, then I suppose the debugging and hair-pulling that comes after that is me getting good entertainment value...?
 
The AI models have a habit of giving a generic solution and then adapt the syntax to the requested language. For most languages arrays are of a defined size and so passing an array and requesting its size would work.
In c an array is a pointer that defaults to the location of a block of allocated memory. Other than that initial declaration / initialisation pointers and arrays are completely interchangeable. Both of these will create an int pointer that points to a block of memory large enough to hold 5 ints.
C:
int myArray[5];
int* myPtr = (int*)malloc(5*sizeof(int));
It is perfectly valid to use *(myArray+1) to access the second element of the array or myPtr[1] to access the second element of the malloced memory.

The only big differences between these two is which memory pool is used (stack Vs heap) and whether you need to manually free it or it happens automatically when the variable goes out of scope.
 
Correct — in C, arrays and pointers are closely related in usage. The key differences are:


  • int myArray[5]; allocates memory on the stack; size is fixed at compile-time and memory is freed automatically.
  • int* myPtr = malloc(5 * sizeof(int)); allocates memory on the heap; size is dynamic, but you must manually free(myPtr).

Access syntax is interchangeable (myArray[1] ≡ *(myArray + 1)), but lifetime and memory management differ.
 
@putty: reply with some unrelated comment (joke, etc.) and/or correct my intentoinal mispelling mitsakes as an indirect indication that you are not a chatGPT bot . . . I'll wait . . .

Mark J Culross
 
Also, for Future Readers ... it's not necessarily true that int myArray[5]; allocates on the stack - that's only true if the statement appears inside a function.
 
Back
Top