|
Randor wrote: Could you point out what you are referring to?
I should have read your original post more carefully....
The following is part of what the OP posted.
"allocate memory for an int every frame."
To me the terms I underlined are significant.
Most compilers that I have ever seen, not just C/C++, use a 'stack frame' to manage the variables within a method.
The allocation, far as I can tell, is how the OP is referring to, because the post specifically uses those terms.
But it is still up to the compiler. As a matter of fact at least at one time compilers at one time made a big deal (advertising) that the method variables were managed as CPU 'register' values and were not put onto the stack frame at all. And that is definitely not in the specifications for C or C++.
I do know, because I looked at the assembly that compilers used to emit (and at times modified it) that compilers at one time did nothing more than allocate variables on the stack frame sequentially. I might even recall reading an article that a developer would need to manage variables more carefully to limit that. (I can perhaps recall the suggestion that all variables should be declared at the top of the method for that very reason.)
Now back to what you posted...
For what you posted the "scope" refers to where the variable is visible from. At the language level.
That does NOT specify how the compiler is to manage the variables.
Do you have a different specification, which would probably need to be after C99, that does state how the stack frame is to be managed? I say C99 since I was familiar with that one and I am rather certain that it says nothing at all about the stack frame.
I also looked through my books for "C++ Programming Language" and "C Programming Language" and found nothing at all about the stack frame. I did not expect to find it.
I did look through the Dragon book where I would expect this to be discussed. It doesn't use that term instead it uses the term 'activation record'. It discusses how the activation record can be managed by a stack.
|
|
|
|
|
The C99 standard is here: C99 Standard
Scope starts at 6.2.1
But you would probably want to read: 6.2.4 Storage durations of objects
I use to read all these many years ago, I stopped reading the specs after C11
At Microsoft we had a huge internal mailing list where everyone got to watch the compiler team go back and forth over the new language features. The gatekeeper of the STL library had the initials S.T.L. I always thought that was funny.
|
|
|
|
|
Randor wrote: 6.2.4 Storage durations of objects
But 6.2.4 says nothing about how a stack frame is built.
Just as with your other reference it explains what the compiler must enforce but not how it must enforce it.
Following is the only thing that relates to the language
4 An object whose identifier is declared with no linkage and without the storage-class specifier statich asautomatic storage duration.
5 For such an object that does not have a variable length array type, its lifetime extends from entry into the block with which it is associated until execution of that block ends in anyway.(Entering an enclosed block or calling a function suspends, but does not end, execution of the current block.) If the block is entered recursively, a new instance of the object is created each time. The initial value of the object is indeterminate. If an initialization is specified for the object, it is performed each time the declaration is reached in the execution of the block; otherwise, the value becomes indeterminate each time the declaration is reached.
The second paragraph is the only one even close to relevant and basically makes the same point as your other reference.
It does not specify how it is built on the stack frame.
Again a compiler writer could make a fully compliant compiler which used new slots on the stack frame for each block. Or it could reuse existing ones. Both implementations are compliant.
The referenced section allows a compiler writer to produce optimized code that reuses the slots. And they cannot be considered non-compliant if someone attempts to use a declared variable in a block outside the block (for example via a pointer.)
Consider exactly that case - using a pointer outside the block.
1. Compiler A uses new slots so the code works.
2. Compiler B reuses slots so the code doesn't work.
The user complains that Compiler B is non-compliant. The creators can tell them explicitly that they are using code that the spec does not support.
But it says nothing about Compiler A. Compiler A is NOT required to attempt to determine that a pointer belongs to a variable that goes out of scope. (Similar to having a method return a pointer to a local variable.) The compiler might choose to warn about that but is not required to do so.
|
|
|
|
|
Thanks for writing all that. The ironic part of this conversation is that the answer to the question at the top of this thread been the same since C99.
|
|
|
|
|
I'm still not clear about this.
If variables X and Y are defined in a block, must they both be available throughout the lifetime of that block?
If the compiler does a complete flow analysis, detecting that variable X is only used in the first half of the code, and variable Y only in the second half, with no overlapping use possible for any possible execution path, can then X and Y share the space?
For the running code, sharing would be OK. A debugger might display both X and Y as soon as the block is entered, and all the time until the block is left. If X and Y share a location, then Y would be incorrectly displayed for the first half, X for the second half. Does anything in the C++ standard forbid this? Does the language standard at all relate to tools like debuggers, or only to the executing program code itself?
|
|
|
|
|
trønderen wrote: If variables X and Y are defined in a block, must they both be available throughout the lifetime of that block?
Via the spec? No. The spec is just asserting that they are not 'available' outside the block.
In reality, in terms of the compiler emitted code? Probably.
Consider what difficulty the compiler has in determining what the scope of A is in the following. Keep in mind that the code can get much more complex.
{
int A = 3;
int *B = &A;
int **C = &B;
doit(C);
}
|
|
|
|
|
jschell wrote: Consider what difficulty the compiler has in determining what the scope of A is in the following. Keep in mind that the code can get much more complex.
{
int A = 3;
int *B = &A;
int **C = &B;
doit(C);
} This is the kind of examples that tend to give great respect for those people trying to optimize C/C++ code. And also a great pity with them.
|
|
|
|
|
trønderen wrote: If variables X and Y are defined in a block, must they both be available throughout the lifetime of that block?
jschell wrote: Via the spec? No.
Regarding C99 the lifetime is defined in 6.2.4 paragraph 2:
The lifetime of an object is the portion of program execution during which storage is
guaranteed to be reserved for it. An object exists, has a constant address,25) and retains
its last-stored value throughout its lifetime. 26) If an object is referred to outside of its
lifetime, the behavior is undefined. The value of a pointer becomes indeterminate when
the object it points to reaches the end of its lifetime.
Then paragraph 5 defines how that "lifetime" is applied in the block scope.
modified 29-Sep-23 19:38pm.
|
|
|
|
|
Randor wrote: Then paragraph 5 defines how that "lifetime" is applied in the block scope.
Just curious - what exactly do you think the scope is when a CPU register is used rather than a stack frame slot?
|
|
|
|
|
jschell wrote: Just curious - what exactly do you think the scope is when a CPU register is used All rules go out the window on the optimization pass. Could even optimize away the objects. But the initial code generation will follow the rules above if the compiler is "C99 compliant"
In fact if /LTCG is enabled entire functions could be removed/combined. Compiler optimization and link time code generation is an entirely different discussion.
|
|
|
|
|
To me, that sounds like one of two:
Either, the C/C++ scoping/life time rules apply to the source code only and how it 'conceptually' is executed, and does not relate to the compiled code and how it actually runs.
Or, the C/C++ standard does not, strictly speaking, allow optimization of the kinds that we are talking about here, such as allocating a variable in a register, reuse of stack locations within a single scope, or removing variables entirely.
I don't know which is correct. To me, it is the running code that matters, even if the correct interpretation of the standard is that it doesn't care about the generated code.
|
|
|
|
|
Yeah,
I guess the only way to answer a C/C++ language question is to point at the C/C++ standards that define the language.
It reminds me of this old blog post: This processor has no stack
|
|
|
|
|
The question is how the C/C++ standard defines its own scope. How far does the authority of the standard reach. How does it define which parts of the system must be compliant to the standard for the system to call itself standard compliant. Does it stop at the parser, or could an optimizer have any effect on whether the system is compliant.
This is more a "meta-question", not something you will find in the syntax description pages. If you find it at all in the standard text about the authority and scope of the standard itself, and which parts of a system must comply to the standard, it is probably in some introductory or concluding chapters. I do not expect it to be in the standard itself, and if it is there, I expect the wording to be so formalistic that you have to have some experience from working in the standards committee to get it right.
I did pick up a copy of a late-in-the-process draft documents, but the final document is too high priced that I am willing to pay that much for the spec of a language I use very rarely nowadays. I tried to read the drafts, but they were too huge for me to read every word - and in particular: to understand every word of it 
|
|
|
|
|
trønderen wrote: they were too huge for me to read every word - and in particular: to understand every word of it
Yeah, me too.
The last language spec I read was C11 and C++11 and haven't looked at C++14 or higher. I've learned some the new language features but not reading the specs anymore unless I have to.
|
|
|
|
|
Randor wrote: It reminds me of this old blog post: This processor has no stack Slight sidetrack: Fortran did not allow recursion until the Fortran 90 version; the memory usage was fixed. One of the arguments brought up to keep Fortran alive was that you never risked a stack overflow in Fortran; it was safer than stack languages. Besides: Since there were no stack management, the call overhead was reduced.
At least in its first standard version, Ada required recursive functions to be flagged as such in the program text, and that the maximum calling/recursion depth to be statically determinable at compile time, so that the maximum stack requirement can be calculated in advance.
CHILL also required recursive functions to be marked as such, but I am not sure if you were allowed to code an 'uncontrolled' recursion. At least, to manually search for stack overflow problems, you could limit the search to functions marked as recursive.
|
|
|
|
|
trønderen wrote: Slight sidetrack If you want to get further sidetracked you can watch Aaron Ballman go over all the C23 changes. He's on both the C and C++ committees. Video was posted yesterday.
I'm not going to be using any C23 anytime soon, but I try to keep up with all the language changes.
|
|
|
|
|
He is clear and concise, and he definitely knows what he is talking about. The seventy minutes were certainly not wasted time.
Yet, for at least a third, maybe half of it, my immediate reaction as a C# developer is either, 'Do you still have to struggle with this?' Or, 'Have you really been without this until now?' ... I sort of knew; I was using C a few years back. This video certainly doesn't make me long back to C.
Also, I am (not) surprised to see that features are adopted from C++, after C++ has adopted them from C# (and other languages). It is presented as if C++ was the inventor of several mechanisms that were only slowly adopted from from other languages. I have a strong feeling of NIH.
It is interesting to see what 'they' - those in other camps - are doing. Even if I don't see a single thing that makes me exclaim 'Why don't they do it that way in C# as well?'
|
|
|
|
|
trønderen wrote: the C/C++ scoping/life time rules apply to the source code only and how it 'conceptually' is executed, and does not relate to the compiled code and how it actually runs.
Yep.
So as per the OP
"will allocate memory for an int every frame."
To me that is a question about how the compiler emitted code works and has nothing to do with the spec.
It seems to me to be obviously referring to the stack frame (emitted code.) And for comparison both parts of that are meaningless if the compiler emitted code uses a CPU register.
Compliance for the spec only applies to the 'scope' which means the visibility and the lifetime of the data.
Thus no one can use a compliant compiler and then complain when they attempt to circumvent the scope, for example by using a pointer. Because a compliant compiler can (but is not required) to reuse either a stack frame slot or a CPU register.
It is however possible to use a compliant compiler and deliberately circumvent the scope and it will work. At least for that compiler and that version of the compiler. Might even work in some cases in the same emitted binary and not others depending on how the compiler (not the spec) decides to optimize.
|
|
|
|
|
To add to what jschell said about the spec, I'd point out that modern compilers are surprisingly good at code analysis. I often run into situations where I ask the debugger for a variable value on an optimized build and get a message similar to "variable optimized away". So the compiler only needs to produce code as if a variable exists. If it can deduce the value of the variable for its lifetime, it doesn't need to actually provide storage space for it. There's obviously things you might do that would require there actually be space for it on the stack, like passing its address to a function, for example.
Keep Calm and Carry On
|
|
|
|
|
k5054 wrote: To add to what jschell said about the spec Which part was correct?
|
|
|
|
|
I understand variable scope to work the way (I think?) jschell is describing.
That is, of course the i itself is bounded by {} of the for loop (if I recall correctly) and yet I don't know that the spec is clear where the var has to be allocated/deallocated except perhaps limiting it to the method in question - I'm not even sure there to be honest.
I don't have the head for reams of specifications so I tend to avoid them unless I need to resolve something specific. The above just comes from what I remember of using it, plus a bit of an educated guess - not all systems *could* potentially allocate variables at greater granularity than a method. Some assemblers require you to pre-reserve the entire stack frame you'll be using for that function. In that case, what's a C/C++ compiler to do? The variables effectively live for the entire life of the routine. Sure they're scoped more narrowly than that but the actual physical memory would be there.
That's my understanding and I could be very wrong.
Check out my IoT graphics library here:
https://honeythecodewitch.com/gfx
And my IoT UI/User Experience library here:
https://honeythecodewitch.com/uix
|
|
|
|
|
Calin Negru wrote: the program will allocate memory for an int every frame.
No, it does not. An int is a "value type", and value types are usually allocated on the stack, not the heap. Also, in your example, the anint variable is only allocated (or "pushed" onto the stack once, upon execution of the loop initializer. The value in that stack location is changed on every iteration of the loop. Once the loop is complete, that variable is popped off the stack and will no longer exist.
Of course, all of this is a bit generalized and is not accurate in all cases. It is possible to allocate a value type on the heap, generally called "boxing".
|
|
|
|
|
|
Dave Kreskowiak wrote: It is possible to allocate a value type on the heap, generally called "boxing". Are you referring to managed code or native code with this statement?
The difficult we do right away...
...the impossible takes slightly longer.
|
|
|
|
|
Managed. In my illness induced stupor, I thought we were in the C# forum.
|
|
|
|