>>I thought that any variable declared within a function was placed in the stack, and only global variables were allocated on the heap?
It really is better to think of memory as coming in two variants; the first is your McDonald's happy meal prepackaged static memory. The second is "have it your way" Burger King dynamic memory.
Static memory is named storage controlled by the compiler, like
a is static memory, b is a pointer to static memory, behind the pointer there's named storage that the compiler takes care of. Next is dynamic memory, this is anonymous storage that is *only* referenced by a pointer
int a = 10;
b = &a;
The compiler doesn't do any housekeeping and there's no name behind the pointer, just unnamed memory that you have control over. When all you think about is static and dynamic you have a lot less confusion and you don't run into problems like this
b = malloc(sizeof(int));
char a; /* Static memory! */
fgets(a, 100, stdin);
return a; /* Uh-oh, static memory gets cleaned up, you return garbage */