Yes. I hope that didn't cause any confusion.
>
>> For systems that don't have a normal stack, I think there are perhaps
>> three main categories. The first is a few ancient systems designed from
>> before processors settled down to the modern standard form (such as
>> systems with ones' complement arithmetic, 36-bit ints, and that kind of
>> thing). You can be confident that your C++ code will not be running on
>> these systems unless you are employed specifically to write code for
>> them - people who have such machines don't run new or unknown code on them.
>>
>> The second is for very limited microcontrollers (like 8051) where normal
>> stack usage is very inefficient or non-existent (such as some AVR Tiny's
>> and the smallest PIC microcontrollers). Again, you know you are
>> programming for these - and it is highly unlikely you are using C++ on them.
>>
>> The third is for esoteric or specialised processors that are sometimes
>> used in ASICs or FPGAs, or as coprocessors in electronics. These might
>> have no stack - or might have more than one. Again, you know you are
>> using these, and again C++ is very unlikely.
>
> As I understand it, there are still mainframe systems that
> allocate function call activation records on a heap. When you
> call a function, space for its local variables is allocated
> by the equivalent of malloc or new, and returning from a
> function deallocates space by the equivalent of free or delete.
That's my first group. Such mainframes are ancient systems. They are
still some in use - there are perhaps even a few that are still
produced. But they are used in critical systems - systems that must
work correctly without pause over years or decades. Little completely
/new/ software is made for these old systems - primarily code for them
would be maintaining or expanding existing code. And the people
programming or administrating these machines don't use some code they
found off github - the people writing code that runs on these systems
/know/ they are doing that.
More modern mainframes, based on modern ISAs like Power, x86-64, SPARC,
etc., are a different matter. But on these systems you have will
typically have conventional stacks. In fact, many new mainframe
installations are primarily as hosts for Linux virtual machines, and
thus the coding is normal Linux programming with conventional stacks.
(Mainframes are not something I work with, so any corrections to what I
write would be valued.)
> I wouldn't bet against such a mechanism becoming popular again in
> the future. C++ code that doesn't go out of its way to depend on
> implementation-specific details shouldn't even notice the difference.
(I agree that C++ code should not see the details of any differences
here, unless it is very specialised for some purpose.)
I'd be surprised to see it become popular again. There are potential
security and reliability benefits from having function stack frames
allocated as separate blocks, as well as the possibility of more
efficient use of memory (especially in multi-threaded or coroutine
environments). But the costs in efficiency of the code are far from
insignificant unless you have dedicated hardware support - and "normal"
processors do not have that. You can get most of the benefits of
growable stacks on most systems by just using the OS's virtual memory
system, which already exist. And if you need something more
sophisticated, split stacks offer a better solution than heap-allocated
function stack frames.
Still, predictions are hard, especially about the future. The computing
world has gone from early days of trying out a wide variety of hardware
systems, to consolidating on "C friendly" architectures. But as such a
high proportion of software these days is written in higher level
languages, the details of the underlying architectures have become less
and less relevant. So maybe new architectures will change this.
>
> For the second and third, if the system is unable to allocate
> space for automatic objects, then the system is non-conforming
> (or has absurdly small capacity limitations). Of course you can
> usually assume that that your C++ implementation is conforming.
> Sometimes you might need to work with a subset implementation and
> work around missing features. (I suspect that C subsets are more
> common than C++ subsets for such systems.)
On some small microcontrollers, automatic storage is implemented
primarily as static addresses in ram. To do this, the toolchain has to
understand the reentrancy of functions to know that the function (or at
least the automatic variables) cannot be "live" more than once at a
time. This is done with a combination of compiler extensions to mark
functions as reentrant or not (often the default is "not") and
whole-program optimisation. Smart optimisation and lifetime analysis
lets the compiler/linker overlap these static areas to reduce ram usage.
Yes, C is usually used on such systems, rather than C++. But sometimes
C++ is used too. And yes, you usually have a variety of limitations and
non-conformity. (For C++, for example, you can be confident that
exceptions and RTTI are not supported.)
Again, the point is that you know you are working on such systems.
Conversely, if you don't know that you are writing code for something
like this, you are not - and can therefore assume you have a more
"normal" target.
>
>> My conclusion is that when you code in C++, you can assume you have a
>> "stack" in the conventional sense, because you'd know if you didn't have
>> one. And it is useful to know about it, because it is a lot more
>> efficient to put objects on the stack than on the heap. But the
>> /details/ shouldn't matter - such as whether the object is actually on
>> the stack, or held in registers, or optimised away fully or partially.
>> And I can think of no good reason why one might want to know if a
>> particular object is on the stack or on the heap in a context where it
>> is not obvious (such as in the function that defines the object).
>
> Sure, it's probably reasonable to assume that allocation and
> deallocation is more efficent for objects with automatic storage
> duration than for objects allocated via new/malloc. If your code
> runs on a system where that isn't true, but the system is still
> conforming, your program won't break.
Yes. (It's better to be potentially a little less efficient than a
little less correct!)
>
> The kind of assumption I was thinking of is, given that x0, x1,
> and x2 are objects defined in nested function calls, assuming that
> either (&x0 < &x1 < &x2) or (&x0 > &x1 > &x2) (that's pseudo-code;
> "<" doesn't chain that way), and that their addresses don't differ
> by a whole lot (maybe a few kilobytes for typical functions).
> That assumption is going to be valid on a system with a conventional
> stack, and invalid on a system that allocates function call
> activation frames on a heap. But even if you're certain that your
> code will only run on conventional systems, I can't think of any
> good reason to make your code rely on those assumptions.
It's surely going to be undefined behaviour on all systems? Of course,
that doesn't mean it won't work "as expected" on most systems, and you
can usually convert the addresses to uintptr_t and do the comparisons.
I agree entirely that your code shouldn't be using code like that - it's
unlikely to make sense, and isn't necessarily going to be stable. The
relationship between source code functions and local variables, and
their actual implementation, is very far from obvious with modern
optimising compilers.
(Implementations of functions like memcpy and memmove can be interested
in this sort of thing, but as standard library functions, these
implementations get to know more about the system details and can
"cheat" as necessary.)
>
> On the other hand, if you're examining object addresses in a
> debugger, it's perfectly reasonable to make use of whatever you
> know about the system.
>
Yes.
> Well written C++ code *shouldn't care* whether it's running on
> a system with a contiguous stack or not, as long as the system
> correctly supports automatic storage allocation and deallocation
> in some manner.
>
Agreed.