Do statically allocated arrays in C use all of their memory even when some of their elements are not specified?
If you have int array[1000000];
and only use a few of its initial members, then in some circumstances (if array
is either static
or a local of if it's a global and you're linking statically with link time optimizations) your toolchain can shrink/eliminate the array under the as-if rule.
(Note that global and static variables are not in effect uninitialized--the C standard mandates that they be zero-initialed.)
Gcc and clang do it, and clang does it even with malloc
ated arrays to the point that the entire malloc-free
pair can be eliminated:
Example:
#include <stdio.h>
#include <stdlib.h>
//gcc and clang optimize out the array, only using
//the constants 1 and 10
int pr(void)
{
int array[1000000];
array[1] = 1;
array[10] = 10;
return printf("%d %d", array[1], array[10]);
}
//clang optimizes out the dynamic allocation of array,
//only using the constants 1 and 10
int pr1(void)
{
int r;
int *array = malloc(1000000);
if(!array) return -1;
array[1] = 1;
array[10] = 10;
r = printf("%d %d", array[1], array[10]);
free(array);
return r;
}
Example output assembly on x86-64 clang with -O3:
pr: # @pr
mov edi, offset .L.str
mov esi, 1
mov edx, 10
xor eax, eax
jmp printf # TAILCALL
pr1: # @pr1
mov edi, offset .L.str
mov esi, 1
mov edx, 10
xor eax, eax
jmp printf # TAILCALL
.L.str:
.asciz "%d %d"
Check it out at https://gcc.godbolt.org/z/UmiA34.
Such optimizations are nonportable and twitchy, however. The simplest things such passing a pointer to an array to a function defined in a different translation unit can turn them off. I would avoid relying on them.
C programming language is defined in terms of abstract machine. The behaviour of a program is described as it would happen if executed in an abstract machine that has the same characteristics as the target environment. The C standard defines that in this abstract machine storage is guaranteed to be reserved for objects for their lifetime, so
int array[1000000];
will have sizeof (int) * 1000000
bytes memory reserved for its lifetime (which is until the end of the scope where the array was defined) and so does the object allocated with
int *array = malloc(sizeof (int) * 1000000);
where the lifetime ends at the corresponding free
. That's the theory.
However the standard says that any compiler is conforming even if it produces a program that when run behaves as if it was run in the abstract machine according to its rules. This is called the as-if rule. So in fact if you write something like
for (int i = 0; i < 100; i++) {
int *p = malloc(sizeof (int) * 1000000);
}
the compiler can produce an executable that does not call malloc
at all since the return value is not used. Or if you just use p[0]
it can notice that actually you could live with int p_0
instead and use it for all calculations. Or anything in between. See this program for an example:
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *array = malloc(1000000);
int tmp;
scanf("%d", &tmp);
array[0] = tmp;
array[1] = array[0] + tmp;
printf("%d %d\n", array[0], array[1]);
}
Compiled with GCC 9.1 -O3
for x86-64 it produces
.LC0:
.string "%d"
.LC1:
.string "%d %d\n"
main:
sub rsp, 24
mov edi, OFFSET FLAT:.LC0
xor eax, eax
lea rsi, [rsp+12]
call __isoc99_scanf
mov esi, DWORD PTR [rsp+12]
mov edi, OFFSET FLAT:.LC1
xor eax, eax
lea edx, [rsi+rsi]
call printf
xor eax, eax
add rsp, 24
ret
which has 2 call instructions: one for scanf
and one for printf
but none for malloc
! And how about
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int array[1000000];
int tmp;
scanf("%d", &tmp);
array[0] = tmp;
array[1] = array[0] + tmp;
printf("%d %d\n", array[0], array[1]);
}
The output is
.LC0:
.string "%d"
.LC1:
.string "%d %d\n"
main:
sub rsp, 24
mov edi, OFFSET FLAT:.LC0
xor eax, eax
lea rsi, [rsp+12]
call __isoc99_scanf
mov esi, DWORD PTR [rsp+12]
mov edi, OFFSET FLAT:.LC1
xor eax, eax
lea edx, [rsi+rsi]
call printf
xor eax, eax
add rsp, 24
ret
which is identical.
In practice you can not depend on any such behaviour, as none of it is guaranteed, it is just a possibility allowed for compilers to optimize.
Notice that in case of global objects with external linkage, the compiler wouldn't know if any other translation units to be linked could depend on the array having the defined size, it would often have to produce output that actually has the array in it.