Correcting the answer
No it doesn't default to 0. It's undefined behaviour. It just happened to be 0 in this condition, this optimization and this compiler. Trying to access uninitialized or unallocated memory is undefined behaviour.
Because it's literally "undefined" and the standard has nothing else to say about this, your assembly output is not going to be consistent. The compiler might store the array in an SIMD register, who knows what the output will be?
Quote from the sample answer:
and the forth loop prints the default array value of zero since nothing is initialized for element 3
That's the most wrong statement ever. I guess there's a typo in the code and they wanted to make it
int x[4] = {120, 200, 16};
and mistakenly made it x[4]
into just x[]
. If not, and it was intentional, I don't know what to say. They're wrong.
Why isn't it an error?
It's not an error because that's how the stack works. Your application doesn't need to allocate memory in the stack to use it, it's already yours. You may do whatever with your stack as you wish. When you declare a variable like this:
int a;
all you're doing is telling the compiler, "I want 4 bytes of my stack to be for a
, please don't use that memory for anything else." at compile time. Look at this code:
#include <stdio.h>
int main() {
int a;
}
Assembly:
.file "temp.c"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
endbr64
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6 /* Init stack and stuff */
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret /* Pop the stack and return? Yes. It generated literally no code.
All this just makes a stack, pops it and returns. Nothing. */
.cfi_endproc /* Stuff after this is system info, and other stuff
we're not interested. */
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu 11.1.0-1ubuntu1~20.04) 11.1.0"
.section .note.GNU-stack,"",@progbits
.section .note.gnu.property,"a"
.align 8
.long 1f - 0f
.long 4f - 1f
.long 5
0:
.string "GNU"
1:
.align 8
.long 0xc0000002
.long 3f - 2f
2:
.long 0x3
3:
.align 8
4:
Read the comments in the code for explanation.
So, you can see int x;
does nothing. And if I turn on optimisations, the compiler won't even bother making a stack and doing all those stuff and instead directly return. int x;
is just a compile-time command to the compiler to say:
x is a variable that is a signed int. It needs 4 bytes, please continue declaration after skipping these 4 bytes(and alignment).
Variables in high-level languages(of the stack) only exist to make the "distribution" of the stack more systematic and in a way that it's readable. The declaration of a variable is not a run-time process. It just teaches the compiler how to distribute the stack among the variables and prepare the program accordingly. When executing, the program allocates a stack(that's a run-time process) but it's already hardcoded with which variables get what part of the stack. For eg. variable a
might get -0(%rbp)
to -4(%rbp)
while b
gets -5(%rbp)
to -8(%rbp)
. These values are determined at compile time. Names of variables also don't exist in compile time, they're just a way to teach the compiler how to prepare the program to use its stack.
You, as the user can use the stack as freely as you like; but you may not. You should always declare the variable or the array to let the compiler know.
Bounds checking
In languages like Go, even though your stack is yours, the compiler will insert extra checks to make sure you're not using undeclared memory by accident. It's not done in C and C++ for performance reasons and it causes the dreaded undefined behaviour and Segmentation fault to occur more frequently.
Heap and data section
Heap is where large data gets stored. No variables are stored here, only data; and one or more of your variables will contain pointers to that data. If you use stuff that you haven't allocated(done at run-time), you get a segmentation fault.
The Data section is another place where stuff can be stored. Variables can be stored here. It's stored with your code, so exceeding allocation is quite dangerous as you may accidentally modify the program's code. As it's stored with your code, it's obviously also allocated at compile time. I don't actually know much about memory safety in the data section. Apparently, you can exceed it without the OS complaining, but I know no more as I'm no system hacker and have no dubious purpose for using this for malicious intents. Basically, I have no idea about exceeding allocation in the data section. Hope someone will comment(or answer) about it.
All assembly shown above is compiled C by GCC 11.1 on an Ubuntu machine. It's in C and not C++ to improve readability.
-fsanitize=address
(on Clang, different compilers need different flags). – Runkelint x[4] = {120, 200, 16};
– Haematoblast120 200 16 3
but in reality you are unlucky that the program didn't crash because at least then you would know you aren't allowed to do that. And array bounds checking is possible: #4779052 you can use-O -fbounds-check
which will sometimes catch it. Here is your program with the correct error message: godbolt.org/z/GG5Ts15E5 – Stinkpotargv
array works, or assuming that it applies to all arrays.argv
is required to have an extraNULL
element. – Kushner[4]
inint x[]
. Either that or the prof himself just made an typo. I can't believe a friggin PROFESSOR would make such a mistake, truly believing it to be correct ... – Sklar4
inint x[4]
, as other comments have pointed out. I certainly thought careless at first, until I noticed chux's comment that it would be correct if the declared size of the array was big enough and larger than the initializer list. So let's be careful about being rude about it; at best this was an innocent but highly confusing mistake, written about code that existed in the prof's head, not what the students got. At worst, they don't have a clue how it works, or were assuming that fresh stack memory in main is 0. – Impersonalchar
containing 0. – Excitement