1

I'm inspecting where the memory is used in an application which runs on Arduino. To my surprise, there are several locations where 100 to 200 bytes are consumed by simply calling a function. Example:

void Dispatcher::processCommand() {
    ...
    displayMemory();
    this->process(this->instruction);
}

void Dispatcher::process(const Instruction &instruction) {
    displayMemory();
    if (instruction.length > 0) {
        ...
        return;
    }

    ErrorResponse::componentNotFound(instruction).output(this->serial);
    TerminationResponse(instruction.commandId, 0xFF).output(this->serial);
}

The first call to displayMemory() will report that there is 351 bytes left. The second one will tell that only 159 bytes remain (that is, 192 bytes less).

If I alter the body of the process function, the difference between the two memory reports vary. For instance, if I remove the two last lines, the difference drops from 192 bytes to 12 bytes.

I don't understand why this happens. I was thinking that between the moment I'm ready to call a function and a moment when the function starts, the memory should be used exclusively by the additional element in the call stack (the aforementioned 12 bytes maybe?) and the copying of the parameters passed by value (none here). Why is this not the case? What really happens under the hood?

0
3

The clue is in those last two lines:

    ErrorResponse::componentNotFound(instruction).output(this->serial);
    TerminationResponse(instruction.commandId, 0xFF).output(this->serial);

Let's tear them apart and see what they actually do.

ErrorResponse::componentNotFound(instruction).output(this->serial);

Call the static function componentNotFound with your instruction, then take the object that is returned, and call the output() method on it passing this->serial as a parameter.

The hint there is take the object that is returned.

componentNotFound() returns an object. That object has to be stored somewhere. That "somewhere" is on the stack. And stack space is allocated during the function preamble. That's the bit of code that runs at the start of the function allocating stack, pushing registers to the stack, etc.

The same happens in the second line:

TerminationResponse(instruction.commandId, 0xFF).output(this->serial);

Create a new TerminationResponse object and call the output method.

All that goes on the stack.

It's not the calling of the function that is using the memory, it's what the function is doing that is using the memory.

2

A function's data-memory space is reserved on the stack when the function is called, and returned when the function returns. If that function1 calls another, function2, then function1's space remains on the stack and more space is reserved for function2 for the duration of its execution. When function2 returns to function1, function2's memory is released, and finally, when function1 returns, its memory is released.

Now in a little more detail:

When you call function1, the arguments you pass it are put on the stack. When control actually passes to function1, the instruction address following the call, where function1 should eventually return to, is put on the stack. If function1 needs data temporary data space (automatic variables), space for those is put on the stack. (Ditto for function2, if one is called from function 1; and so on.)

When each of those functions returns, their automatic data space (just the space, not the data!) and the return address are removed from the stack; the caller removes the space for the arguments it had pushed, and execution resumes immediately following the call.

The difference in usage you're seeing depending on the presence or absence of the last two lines of Dispatcher::process(), is the memory (arguments+return address+automatic data) used by those two functions.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.