67

I wrote a C program that accepts integer input from the user, that is used as the size of an integer array, and using that value it declares an array of given size, and I am confirming it by checking the size of the array.

Code:

#include <stdio.h>
int main(int argc, char const *argv[])
{
    int n;
    scanf("%d",&n);
    int k[n];
    printf("%ld",sizeof(k));
    return 0;
}

and surprisingly it is correct! The program is able to create the array of required size.
But all static memory allocation is done at compile time, and during compile time the value of n is not known, so how come the compiler is able to allocate memory of required size?

If we can allocate the required memory just like that then what is the use of dynamic allocation using malloc() and calloc()?

Banex
  • 2,890
  • 3
  • 28
  • 38
Rahul
  • 975
  • 9
  • 25
  • This doesn't sound right. What compiler are you using? – o_weisman Sep 24 '17 at 06:09
  • i am using gcc! in ubantu. – Rahul Sep 24 '17 at 06:10
  • 1
    Why would you do that, instead of the normal "k = (int *) calloc (n, sizeof (int));"? Just to obfuscate your code? – jamesqf Sep 24 '17 at 17:51
  • 30
    @jamesqf How is `int k[n];` an obfuscated version of `k = (int *) calloc (n, sizeof (int));`? I think the former is more readable (if you know VLAs exist). – jcai Sep 24 '17 at 19:38
  • 5
    @jamesqf: performance. With `n` loaded into `rsi` (ready to be the 2nd arg to printf in the x86-64 SysV ABI), `sub rsp, rsi` (one simple asm instruction) is *much* cheaper than a function-call to `calloc`. Although in this case, `k[]` itself isn't used, only `sizeof(k)`, so a good compiler won't bother actually reserving stack space before calling `printf`. Stack memory already hot in L1D cache, and the TLB, so it's a good place for small buffers. It's also extremely cheap to release it, and you can't every get it wrong because the compiler does it for you. – Peter Cordes Sep 24 '17 at 23:21
  • @Arcinde: Obfuscated because (like most things in obfuscated C) it's something that I think many, if not most experienced programmers a) wouldn't know exists; and b) wouldn't believe would actually work if they saw it. I sure didn't. – jamesqf Sep 25 '17 at 04:39
  • @Peter Cordes: How can the compiler know to reserve stack space, when the size of the needed space isn't known until run time? I can see that it might work for small arrays, but what defines "small" or restricts n to being "small"? If there's insufficient stack for a large n, how does it fail gracefully? – jamesqf Sep 25 '17 at 04:47
  • @o_weisman: It's C, not C++, where compilers can optionally support this feature as per C11. – Sebastian Mach Sep 25 '17 at 06:46
  • 3
    @jamesqf: It doesn't check the size, and it doesn't fail gracefully. It's up to the programmer not to write programs that use a VLA that's too large for the implementations they care about running on. (e.g. [8MB stack size in new user-space threads on Linux x86-64](https://unix.stackexchange.com/questions/127602/default-stack-size-for-pthreads)). Generally you segfault if you touch memory below the bottom of the stack and the OS decides that's too much and doesn't grow your stack mapping. It's a bad idea to use a large VLA in a non-leaf function with children that might also use VLAs. – Peter Cordes Sep 25 '17 at 12:52
  • 1
    @jamesqf: That sounds like it's a lot worse than `new` / `delete`, but with modern OSes that overcommit memory, it's barely any worse. You can allocate much more RAM than the OS has physical RAM + swap space, and touching it all might result in the kernel deciding to kill your process. (Linux calls this the OOM killer). http://www.linuxdevcenter.com/pub/a/linux/2006/11/30/linux-out-of-memory.html?page=2. You can make allocation fail gracefully by setting limits on the amount of virtual memory a process can allocate, though, so `malloc` will actually return NULL, but this isn't the default. – Peter Cordes Sep 25 '17 at 13:02
  • the OPs posted code is making use of VLAs (Variable Length Arrays). This is NOT a compile time memory allocation, but rather a runtime memory allocation. The VLA feature is a recent addition to the language. – user3629249 Sep 26 '17 at 19:49

5 Answers5

75

This is not a "static memory allocation". Your array k is a Variable Length Array (VLA), which means that memory for this array is allocated at run time. The size will be determined by the run-time value of n.

The language specification does not dictate any specific allocation mechanism, but in a typical implementation your k will usually end up being a simple int * pointer with the actual memory block being allocated on the stack at run time.

For a VLA sizeof operator is evaluated at run time as well, which is why you obtain the correct value from it in your experiment. Just use %zu (not %ld) to print values of type size_t.

The primary purpose of malloc (and other dynamic memory allocation functions) is to override the scope-based lifetime rules, which apply to local objects. I.e. memory allocated with malloc remains allocated "forever", or until you explicitly deallocate it with free. Memory allocated with malloc does not get automatically deallocated at the end of the block.

VLA, as in your example, does not provide this "scope-defeating" functionality. Your array k still obeys regular scope-based lifetime rules: its lifetime ends at the end of the block. For this reason, in general case, VLA cannot possibly replace malloc and other dynamic memory allocation functions.

But in specific cases when you don't need to "defeat scope" and just use malloc to allocate a run-time sized array, VLA might indeed be seen as a replacement for malloc. Just keep in mind, again, that VLAs are typically allocated on the stack and allocating large chunks of memory on the stack to this day remains a rather questionable programming practice.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
  • how about i use `static` Eg- `static int k[n]` then it is like malloc()? – Rahul Sep 24 '17 at 06:26
  • 6
    @Rahul: C does not support `static` VLAs. If `n` is a run-time value, then `static int k[n]` is not allowed. But even if it were allowed, it wouldn't allocate a new memory block every time. Meanwhile. `malloc` allocates a new block every time you call it. So, there's no similarity to `malloc` here even with `static`. – AnT stands with Russia Sep 24 '17 at 06:26
  • 2
    The wording in "in a typical implementation your k will end up being a simple int * pointer" seems a bit risky. There's plenty of confusion about pointers and arrays out and about as is. – Ilja Everilä Sep 24 '17 at 06:30
  • Ok thanks, but 2 years earlier this program would have given error! by then i knew that we cant do that but it seem things is changes, so thats why it confused me. – Rahul Sep 24 '17 at 06:30
  • 5
    @Rahul: VLA were introduced in C99 standard. Formally, they have been around for about 18 years now. Of course, compiler support took some time. – AnT stands with Russia Sep 24 '17 at 06:31
  • @Ilja Everilä: True. Nevertheless, in a typical implementation VLA just happen to be implemented internally as pointers. Even though non-VLA arrays are certainly not implemented in that way. – AnT stands with Russia Sep 24 '17 at 06:32
  • Then at that time i might have used old compiler, anyway thanks! – Rahul Sep 24 '17 at 06:33
  • 3
    Well I definitely *wouldn't* call this an *`int *` pointer" because there is no such C object here. – Antti Haapala -- Слава Україні Sep 24 '17 at 07:52
  • 1
    @AnttiHaapala That's why I do not call an `int *` pointer, but rather state that it is usually *implemented internally* as a pointer. The question is, after all, more about internal mechanics, not about language-level abstraction. – AnT stands with Russia Sep 24 '17 at 13:29
  • @AnT: we can return VLA fram a function! then it can be as good as `malloc()`! – Rahul Sep 24 '17 at 17:13
  • @Rahul No, we can't return VLA from a function. How? – AnT stands with Russia Sep 25 '17 at 00:52
  • Note that VLA is essentially the equivalent of [alloca() or _alloca()](https://msdn.microsoft.com/en-us/library/wb1s57t5.aspx), which allocates from the stack, and not malloc(), which normally allocates from the heap. Also Microsoft / Visual Studio compilers do not support VLA. – rcgldr Sep 25 '17 at 01:27
  • 2
    @rcgldr: VLA is very similar to `alloca` and is definitely "inspired" by `alloca`. However, `alloca` allocates memory with "function" lifetime: the memory persists until the function exits. I.e. it ignores all block boundaries besides the outermost one - the function body itself. Meanwhile VLA has normal block-based lifetime. In this regard VLA is very different from `alloca`. It is true that Visual Studio does not support VLA to this day. However, it is worth nothing that since C11 VLA is an *optional* feature of the language. – AnT stands with Russia Sep 25 '17 at 03:52
  • @rcgldr: The requirement of declaring variables at the very beginning of the function existed only in pre-standard archaic versions of C from the 1970's. There's no such restriction in C89/90. I'm not aware of any C89/90 C compiler that would impose this restriction. Visual Studio never had this requirement (at least as long as it has been called "Visual Studio"), i.e. even Visual Studio 97's C compiler was compliant with C89/90 requirtements in that regard. – AnT stands with Russia Sep 25 '17 at 04:56
  • `alloca` was typically implemented at library intrinsic level, with no connection to language's scoping rules. It was probably possible to implement a built-in core-language form of `alloca` that would obey block boundaries, but I'm not aware of such implementations. Now we have VLA, which is basically block-scoped `alloca` – AnT stands with Russia Sep 25 '17 at 05:02
  • @rcgldr: Firstly, I don't understand why it is relevant. Secondly, Visual Studio started supporting C99-style variable declarations in C code several versions ago. Since that moment there is no problem declaring variables in `for` loop in C code in Visual Studio. – AnT stands with Russia Sep 25 '17 at 05:15
  • @AnT - deleted my prior comments. My main point was that VLA's are similar to alloca as opposed to malloc. – rcgldr Sep 25 '17 at 06:23
11

In C, the means by which a compiler supports VLAs (variable length arrays) is up to the compiler - it doesn't have to use malloc(), and can (and often does) use what is sometimes called "stack" memory - e.g. using system specific functions like alloca() that are not part of standard C. If it does use stack, the maximum size of an array is typically much smaller than is possible using malloc(), because modern operating systems allow programs a much smaller quota of stack memory.

Peter
  • 35,646
  • 4
  • 32
  • 74
  • 2
    Modern operating systems (and even old and embedded OS's in some cases) allow user configuration of the stack size – M.M Sep 24 '17 at 23:59
10

Memory for variable length arrays clearly can't be statically allocated. It can however be allocated on the stack. Generally this involves the use of a "frame pointer" to keep track of the location of the functions stack frame in the face of dynamicly determined changes to the stack pointer.

When I try to compile your program it seems that what actually happens is that the variable length array got optimised out. So I modified your code to force the compiler to actually allocate the array.

#include <stdio.h>
int main(int argc, char const *argv[])
{
    int n;
    scanf("%d",&n);
    int k[n];
    printf("%s %ld",k,sizeof(k));
    return 0;
}

Godbolt compiling for arm using gcc 6.3 (using arm because I can read arm ASM) compiles this to https://godbolt.org/g/5ZnHfa. (comments mine)

main:
        push    {fp, lr}      ; Save fp and lr on the stack
        add     fp, sp, #4    ; Create a "frame pointer" so we know where
                              ; our stack frame is even after applying a 
                              ; dynamic offset to the stack pointer.
        sub     sp, sp, #8    ; allocate 8 bytes on the stack (8 rather
                              ; than 4 due to ABI alignment
                              ; requirements)
        sub     r1, fp, #8    ; load r1 with a pointer to n
        ldr     r0, .L3       ; load pointer to format string for scanf
                              ; into r0
        bl      scanf         ; call scanf (arguments in r0 and r1)
        ldr     r2, [fp, #-8] ; load r2 with value of n
        ldr     r0, .L3+4     ; load pointer to format string for printf
                              ; into r0
        lsl     r2, r2, #2    ; multiply n by 4
        add     r3, r2, #10   ; add 10 to n*4 (not sure why it used 10,
                              ; 7 would seem sufficient)
        bic     r3, r3, #7    ; and clear the low bits so it is a
                              ; multiple of 8 (stack alignment again) 
        sub     sp, sp, r3    ; actually allocate the dynamic array on
                              ; the stack
        mov     r1, sp        ; store a pointer to the dynamic size array
                              ; in r1
        bl      printf        ; call printf (arguments in r0, r1 and r2)
        mov     r0, #0        ; set r0 to 0
        sub     sp, fp, #4    ; use the frame pointer to restore the
                              ; stack pointer
        pop     {fp, lr}      ; restore fp and lr
        bx      lr            ; return to the caller (return value in r0)
.L3:
        .word   .LC0
        .word   .LC1
.LC0:
        .ascii  "%d\000"
.LC1:
        .ascii  "%s %ld\000"
plugwash
  • 9,724
  • 2
  • 38
  • 51
3

The memory for this construct, which is called "variable length array", VLA, is allocated on the stack, in a similar way to alloca. Exactly how this happens depends on exactly which compiler you're using, but essentially it's a case of calculating the size when it is known, and then subtracting [1] the total size from the stack-pointer.

You do need malloc and friends because this allocation "dies" when you leave the function. [And it's not valid in standard C++]

[1] For typical processors that use a stack that "grows towards zero".

Mats Petersson
  • 126,704
  • 14
  • 140
  • 227
  • In C++, as I’m sure you know, you would use `std::vector` for an array whose size you can determine at runtime, and whose lifetime is the current scope. You can also use `std::vector` for many other purposes. So there is a close substitute. – Davislor Sep 24 '17 at 14:50
  • @Davislor although this wont be on the stack. – lalala Sep 24 '17 at 15:28
  • @lalala `std::vector` allows you to specify the `std::allocator` it will use. IIRC, although I don’t think the standard ever gives you a guarantee that something uses a stack. But if you really want a `std::vector` that calls `alloca()`, you can get one. (You normally wouldn’t, though, because they could be resized.) I’m pretty sure an implementation could even write its C runtime in C++ and implement C VLAs with a C++ `std::vector` under the hood! – Davislor Sep 24 '17 at 16:57
  • C++ also has `std::get_temporary_buffer`. – Davislor Sep 24 '17 at 16:59
  • @Davislor: The C++ abstract machine doesn't have a "stack". It just has automatic vs. dynamic vs. static storage. Having a stack is just an implementation detail, but it's nearly universal so this answer is correct. `std::vector` is not a close substitute for performance, BTW. GNU C++ has VLAs, but unfortunately MSVC doesn't, so you can't just use a VLA for type-safe stack memory when optimizing x86 code that will work on the major compilers. :/ – Peter Cordes Sep 24 '17 at 23:28
  • 1
    @Davislor: [`std::get_temporary_buffer`](http://en.cppreference.com/w/cpp/memory/get_temporary_buffer) is nothing like a VLA or `alloca` in gcc/clang. They try successively smaller `new`: https://godbolt.org/g/VTfNzi, following the letter of the standard and trying to allocate something even if it's smaller than requested. You have to free with `std::return_temporary_buffer`. (OTOH, if a compiler can prove that a pointer doesn't escape, then it could just reserve stack space and make the `return_temporary_buffer` a no-op. But real compilers don't.) – Peter Cordes Sep 24 '17 at 23:58
  • @PeterCordes Your first reply is written as if you’re disagreeing with something I said, but I’m not sure what. I also wrote that the standard doesn’t “give you a guarantee that something uses a stack?” – Davislor Sep 25 '17 at 03:24
  • @PeterCordes Fine, I guess that `std::get_temporary_buffer` isn’t that much like `alloca()`. :) – Davislor Sep 25 '17 at 03:24
  • @Davislor: yeah, I was agreeing that C++ in the abstract doesn't have a stack. And yeah, in theory you could probably implement C VLAs with dynamic storage. But it would be clunky if you want to longjmp to free VLAs as it unwinds the stack back up to the setjmp in a grandparent. But then I switched to disagreeing about that being ok in practice for a *good* implementation, because of performance differences. (I noticed the awkwark switch mid-comment while writing the next, and didn't go back and fix it.) – Peter Cordes Sep 25 '17 at 03:47
  • @PeterCordes Although we’re getting off-topic, you’re right: that is a complication. The implementation of `jmp_buf` and `longjmp` would have to know about any VLAs it needs to destroy, and destroy them, but not automatically destroy any other C++ object. For the special purpose of implementing VLAs, though, a `vector` with custom `allocator` that calls `alloca()` would not need a destructor call during a `longjmp()`. In any case, `vector` has the closest semantics to a VLA in C++. – Davislor Sep 25 '17 at 04:51
  • 1
    `longjmp` is explicitly **not** required to free VLA storage. This is called out in C11 7.13.2.1. I assume this is specifically to allow compilers to implement VLAs in the same way they would implement `std::vector`, or as `malloc`/`free` pairs, in situations where stack allocation is difficult. – Alex Celeste Sep 25 '17 at 11:30
0

When it is said that the compiler allocates memory for variables at compile time, it means that the placement of those variables is decided upon and embedded in the executable code that the compiler generates, not that the compiler is making space for them available while it works. The actual dynamic memory allocation is carried out by the generated program when it runs.

Linkon
  • 1,058
  • 1
  • 12
  • 15