I'm studying for a test and I came across something I'm finding hard to understand. We're working with pointers and memory allocation, and I was just fooling around with things, trying to see what changed what. I have this bit of code:
int * arr[10];
for(i=0; i<5;i++)
{
int index = i;
arr[index] = malloc(sizeof(int*));
int i = 2 * index;
*arr[index] = i;
printf("arr [%d] = %d\n", index, *arr[index]); /* should be 0, 2, 4, 6, 8 */
}
But what I've found is that if instead of using *arr[index] = i, I use arr[index] = &i I don't need the malloc. I've always assumed that these two things were essentially the same thing, but there must be some key difference I don't understand to warrant the use of malloc with the one.
I'm actually confused why I need malloc at all here really. I'm fairly new with memory allocation and I don't really understand when it's supposed to be used (obviously) and was wondering if anyone could clear this up for me.
Try this code instead:
int * arr[10];
for(i=0; i<5;i++)
{
int index = i;
int value = 2*i;
arr[index] = malloc(sizeof(int*));
*arr[index] = value;
}
for (i=0; i<5; i++)
{
int index = i;
printf("arr [%d] = %d\n", index, *arr[index]); /* should be 0, 2, 4, 6, 8 */
}
If you make the change you suggest, you would now have undefined behavior. Whereas this code still is valid.
You'd have undefined behavior because *arr[0] now points to a piece of stack memory that has left scope.
Your malloc should actually be malloc(sizeof(int)). You're allocating space for an int, not for a int *.
Written this way:
*arr[index] = i;
Means: Copy the value of i to the memory location pointed to by arr[index] (that was allocated earlier in your code).
arr[index] = &i;
Means: Copy the address of i to arr[index].
In your code i is automatically created inside the for loop and only exists inside that loop. Once you leave the loop (scope) the memory used to store i is then free to part of any newly created variables.
As sharth suggests, try looking at the values outside the original for loop to see some interesting results.
Yeah I think it is hard to understand, because i gets redefined in the middle of the for. I'll rewrite the code right now. I wrote i instead of index and 2*i instead of the redefined i.
int * arr[10];
for(i=0; i<5;i++)
{
arr[i] = malloc(sizeof(int));
*arr[i] = 2*i;
printf("arr [%d] = %d\n", i, *arr[i]); /* should be 0, 2, 4, 6, 8 */
}
You don't acutally need dynamic memory here, you know that array 0-4 will be used. You need dynamic memory, when you don't know how mutch data will you need. This code is written, so that the rest of your code will still work, but there is no malloc.
int array[5];
int **arr=array;
The following code means, that array[index] should point to the memory adress i is stored in. It does not copy the value that is in i, so when you change i, or i gets deleted, this will cause this pointer to be faulty, and cause problems later. You should't do this.
arr[index] = &i
One key difference is that &i will cease to exist once i goes out of scope (or, rather, that piece of memory can be reused for something else... which probably won't contain what you thought it contains).
Edit: I say below you didn't show how i was declared. Actually, you redeclare it, hiding the original value if i used in the loop. Regardless, i is going to go out of scope at the end of the loop or, likely, when the routine ends.
You don't show how i is declared here. However, in most cases, it'd be a local variable or perhaps a parameter passed to a method. In either case, the space for that variable is declared on the stack. You can take the address of that variable with &i, but that variable is going to go away after the method ends and the code pops those values off the stack. You might get lucky and have that value remain untouched for as long as you need it. But the moment another method is called, that value is likely to be overwritten and boom, your program is at best going to behave incorrectly.
You could get away with this if i is declared globally.
Further, you're pointing to the same address even after changing the value of i. If, at the end of your routine, you printed out all of the values of your array, you'd see they were all the same value - the last value you put into the array. That's because each entry in the array points to the same location.
Related
I've written a function that returns an array whilst I know that I should return a dynamically allocated pointer instead, but still I wanted to know what happens when I am returning an array declared locally inside a function (without declaring it as static), and I got surprised when I noticed that the memory of the internal array in my function wasn't deallocated, and I got my array back to main.
The main:
int main()
{
int* arr_p;
arr_p = demo(10);
return 0;
}
And the function:
int* demo(int i)
{
int arr[10] = { 0 };
for (int i = 0; i < 10; i++)
{
arr[i] = i;
}
return arr;
}
When I dereference arr_p I can see the 0-9 integers set in the demo function.
Two questions:
How come when I examined arr_p I saw that its address is the same as arr which is in the demo function?
How come demo_p is pointing to data which is not deallocated (the 0-9 numbers) already in demo? I expected that arr inside demo will be deallocated as we got out of demo scope.
One of the things you have to be careful of when programming is to pay good attention to what the rules say, and not just to what seems to work. The rules say you're not supposed to return a pointer to a locally-allocated array, and that's a real, true rule.
If you don't get an error when you write a program that returns a pointer to a locally-allocated array, that doesn't mean it was okay. (Although, it means you really ought to get a newer compiler, because any decent, modern compiler will warn about this.)
If you write a program that returns a pointer to a locally-allocated array and it seems to work, that doesn't mean it was okay, either. Be really careful about this: In general, in programming, but especially in C, seeming to work is not proof that your program is okay. What you really want is for your program to work for the right reasons.
Suppose you rent an apartment. Suppose, when your lease is up, and you move out, your landlord does not collect your key from you, but does not change the lock, either. Suppose, a few days later, you realize you forgot something in the back of one closet. Suppose, without asking, you sneak back to try to collect it. What happens next?
As it happens, your key still works in the lock. Is this a total surprise, or mildly unexpected, or guaranteed to work?
As it happens, your forgotten item still is in the closet. It has not yet been cleared out. Is this a total surprise, or mildly unexpected, or guaranteed to happen?
In the end, neither your old landlord, nor the police, accost you for this act of trespass. Once more, is this a total surprise, or mildly unexpected, or just about completely expected?
What you need to know is that, in C, reusing memory you're no longer allowed to use is just about exactly analogous to sneaking back in to an apartment you're no longer renting. It might work, or it might not. Your stuff might still be there, or it might not. You might get in trouble, or you might not. There's no way to predict what will happen, and there's no (valid) conclusion you can draw from whatever does or doesn't happen.
Returning to your program: local variables like arr are usually stored on the call stack, meaning they're still there even after the function returns, and probably won't be overwritten until the next function gets called and uses that zone on the stack for its own purposes (and maybe not even then). So if you return a pointer to locally-allocated memory, and dereference that pointer right away (before calling any other function), it's at least somewhat likely to "work". This is, again, analogous to the apartment situation: if no one else has moved in yet, it's likely that your forgotten item will still be there. But it's obviously not something you can ever depend on.
arr is a local variable in demo that will get destroyed when you return from the function. Since you return a pointer to that variable, the pointer is said to be dangling. Dereferencing the pointer makes your program have undefined behavior.
One way to fix it is to malloc (memory allocate) the memory you need.
Example:
#include <stdio.h>
#include <stdlib.h>
int* demo(int n) {
int* arr = malloc(sizeof(*arr) * n); // allocate
for (int i = 0; i < n; i++) {
arr[i] = i;
}
return arr;
}
int main() {
int* arr_p;
arr_p = demo(10);
printf("%d\n", arr_p[9]);
free(arr_p) // free the allocated memory
}
Output:
9
How come demo_p is pointing to data which is not deallocated (the 0-9 numbers) already in demo? I expected that arr inside demo will be deallocated as we got out of demo scope.
The life of the arr object has ended and reading the memory addresses previously occupied by arr makes your program have undefined behavior. You may be able to see the old data or the program may crash - or do something completely different. Anything can happen.
… I noticed that the memory of the internal array in my function wasn't deallocated…
Deallocation of memory is not something you can notice or observe, except by looking at the data that records memory reservations (in this case, the stack pointer). When memory is reserved or released, that is just a bookkeeping process about what memory is available or not available. Releasing memory does not necessarily erase memory or immediately reuse it for another purpose. Looking at the memory does not necessarily tell you whether it is in use or not.
When int arr[10] = { 0 }; appears inside a function, it defines an array that is allocated automatically when the function starts executing (or at certain times within the function execution if the definition is in some nested scope). This is commonly done by adjusting the stack pointer. In common systems, programs have a region of memory called the stack, and a stack pointer contains an address that marks the end of the portion of the stack that is currently reserved for use. When a function starts executing, the stack pointer is changed to reserve more memory for that function’s data. When execution of the function ends, the stack pointer is changed to release that memory.
If you keep a pointer to that memory (how you can do that is another matter, discussed below), you will not “notice” or “observe” any change to that memory immediately after the function returns. That is why you see the value of arr_p is the address that arr had, and it is why you see the old data in that memory.
If you call some other function, the stack pointer will be adjusted for the new function, that function will generally use the memory for its own purposes, and then the contents of that memory will have changed. The data you had in arr will be gone. A common example of this that beginners happen across is:
int main(void)
{
int *p = demo(10);
// p points to where arr started, and arr’s data is still there.
printf("arr[3] = %d.\n", p[3]);
// To execute this call, the program loads data from p[3]. Since it has
// not changed, 3 is loaded. This is passed to printf.
// Then printf prints “arr[3] = 3.\n”. In doing this, it uses memory
// on the stack. This changes the data in the memory that p points to.
printf("arr[3] = %d.\n", p[3]);
// When we try the same call again, the program loads data from p[3],
// but it has been changed, so something different is printed. Two
// different things are printed by the same printf statement even
// though there is no visible code changing p[3].
}
Going back to how you can have a copy of a pointer to memory, compilers follow rules that are specified abstractly in the C standard. The C standard defines an abstract lifetime of the array arr in demo and says that lifetime ends when the function returns. It further says the value of a pointer becomes indeterminate when the lifetime of the object it points to ends.
If your compiler is simplistically generating code, as it does when you compile using GCC with -O0 to turn off optimization, it typically keeps the address in p and you will see the behaviors described above. But, if you turn optimization on and compile more complicated programs, the compiler seeks to optimize the code it generates. Instead of mechanically generating assembly code, it tries to find the “best” code that performs the defined behavior of your program. If you use a pointer with indeterminate value or try to access an object whose lifetime has ended, there is no defined behavior of your program, so optimization by the compiler can produce results that are unexpected by new programmers.
As you know dear, the existence of a variable declared in the local function is within that local scope only. Once the required task is done the function terminates and the local variable is destroyed afterwards. As you are trying to return a pointer from demo() function ,but the thing is the array to which the pointer points to will get destroyed once we come out of demo(). So indeed you are trying to return a dangling pointer which is pointing to de-allocated memory. But our rule suggests us to avoid dangling pointer at any cost.
So you can avoid it by re-initializing it after freeing memory using free(). Either you can also allocate some contiguous block of memory using malloc() or you can declare your array in demo() as static array. This will store the allocated memory constant also when the local function exits successfully.
Thank You Dear..
#include<stdio.h>
#define N 10
int demo();
int main()
{
int* arr_p;
arr_p = demo();
printf("%d\n", *(arr_p+3));
}
int* demo()
{
static int arr[N];
for(i=0;i<N;i++)
{
arr[i] = i;
}
return arr;
}
OUTPUT : 3
Or you can also write as......
#include <stdio.h>
#include <stdlib.h>
#define N 10
int* demo() {
int* arr = (int*)malloc(sizeof(arr) * N);
for(int i = 0; i < N; i++)
{
arr[i]=i;
}
return arr;
}
int main()
{
int* arr_p;
arr_p = demo();
printf("%d\n", *(arr_p+3));
free(arr_p);
return 0;
}
OUTPUT : 3
Had the similar situation when i have been trying to return char array from the function. But i always needed an array of a fixed size.
Solved this by declaring a struct with a fixed size char array in it and returning that struct from the function:
#include <time.h>
typedef struct TimeStamp
{
char Char[9];
} TimeStamp;
TimeStamp GetTimeStamp()
{
time_t CurrentCalendarTime;
time(&CurrentCalendarTime);
struct tm* LocalTime = localtime(&CurrentCalendarTime);
TimeStamp Time = { 0 };
strftime(Time.Char, 9, "%H:%M:%S", LocalTime);
return Time;
}
I've been told at my university that everything is put on the top of the "stack" when working with a function. So, when returning from one, the top of the stack is removed, until we reach the bottom - main(). Suggesting that everything is lost we have made in the local scope of the previous functions.
Most books tell me the same thing.
However, i have come across a number of occurrences, where i used this exact feature.
For instance:
void address (bool** xpp)
{
bool* y = (bool*) malloc(10*sizeof(bool));
y[2] = false;
**xpp = &y;
}
int main(void)
{
bool* x;
bool** xp = &x;
address(&xp);
xp[0] = false;
xp[2] = false;
xp[7] = false;
printf("%d", xp[0]);
printf("%d", xp[2]);
printf("%d", xp[7]);
return 0;
}
In this case, to my understanding, i should not be able to refer to the xp[] array in the main() after address(), because, indeed i have set it's pointer to an arrays first elements pointer, but after returning to main() the array i had created in address() is supposed to be gone. So it should be pointing to nowhere and should pop up an exception.
However, all of the bool xp elements print '0', implying it's success in working.
Well, there are two different answers here. Your question boils down to, "I heard that local variables are lost when they're popped from the stack, but I tried it, and they were still there", but your actual code does not demonstrate the use of local variables on the stack that would be lost; it demonstrates the use of malloc, which is completely different.
And then the second answer is, any time you ask anything like "I heard that X doesn't work, but I tried it, and it worked", you're playing with fire: it might have seemed to have worked, but it wasn't guaranteed to.
Let's look at a slightly different version of your program. Instead of playing with pointers to pointers, I'm just going to have the address function return a pointer to the first element of an array. And instead of an array of bools, I'm going to use an array of int, so I can more easily show interesting numbers. Here's the first version of the code:
#include <stdio.h>
#include <stdlib.h>
int *address()
{
int *p = malloc(5 * sizeof(int));
if(p == NULL) abort();
p[0] = 1; p[1] = 2; p[2] = 3; p[3] = 4; p[4] = 5;
return p;
}
int main()
{
int *a = address();
int i, sum = 0;
for(i = 0; i < 5; i++) sum += a[i];
printf("sum = %d\n", sum);
for(i = 0; i < 5; i++) printf("a[%d] = %d\n", i, a[i]);
}
Function address returns a pointer, but it's a pointer to dynamically-allocated memory, which is guaranteed to stick around even after address returns. The program prints
sum = 15
a[0] = 1
a[1] = 2
a[2] = 3
a[3] = 4
a[4] = 5
which is just what you would expect.
But now let's change function address to not call malloc, but instead, use a local array:
int *address()
{
int la[5];
la[0] = 1; la[1] = 2; la[2] = 3; la[3] = 4; la[4] = 5;
return la;
}
This function is broken. It returns a pointer to the first element of a local array. By the time the function returns to its caller, that array will no longer exist. The pointer can never be useful to its caller; it is virtually guaranteed not to work. Indeed, when I compile this second version, my compiler warns me about it:
warning: address of stack memory associated with local variable 'a' returned
But if Ignore that warning and run the resulting program anyway, here's what I get:
sum = 15
a[0] = 507402241
a[1] = -16764064
a[2] = 0
a[3] = 0
a[4] = 0
This is a very interesting result. The second thing to notice is that the contents of the array are all wrong. We don't see 1, 2, 3, 4, 5; we see some numbers that are clearly garbage. But this was to be expected, because that array la in function address is gone.
But the first and perhaps more interesting thing to notice is that the sum is correct! How did that happen? And the sum doesn't match the contents of the array as printed! It's as if the "broken" pointer returned by the address function worked for a little while, just long enough to compute the "correct" sum, but got trashed later. And, in fact, that's exactly what happened.
After function address returns, the array la is "gone", but the memory on the stack that it was using hasn't been reused or erased yet, so the bit patterns are still there. So the calling function, main, can try to access those bits, and it even seems to get the right answer -- it computes the same sum, 15. It's important to note that this is absolutely not guaranteed to work; you would never want to depend on it in a real program -- it just happens to work.
But then, having computed the sum, main calls printf to print it out. And printf is a function that gets called -- a big, complicated function -- and it does all sorts of stuff, and allocates all sorts of its own variables on the stack. So that's when the stack memory that had been allocated to the la array actually gets overwritten. So that's why, when the last half of the main function tries to print out the array, it's garbage.
There's another point to make and that has to do with addresses. When we worry about the local array la getting lost or not, there are two questions to ask: do we lose the contents of the array, and, do we lose the pointer to the array? And in answering those questions, we encounter a significant fact: a function can still, perfectly well, return a value, even though the function's local (stack) storage has gone away.
To see this, consider the function
int five()
{
int r = 5;
return r;
}
When function five returns, its local variable r goes away. But the caller who says
int x = five();
has no problem, because as function five returns, and even as function fives local variable r is being allocated, the return value 5 is being copied into the caller's own variable x, so it's not lost.
But with all of that said -- and besides the question of whether the array is a local (stack) array or allocated with malloc -- there's also something wrong, sort of differently wrong, with he way you're taking y's address. y is a local variable, so after the function exits, &y is bogus, and not guaranteed to work, no matter where you've stashed it. (But, again, it might seem to work, for a little while, until something else overwrites the stack.)
How is it possible to access a functions variable outside the function?
Assuming it's not declared static you simply cannot, for the reason you mentioned: It's gone the moment the function had been left.
after returning to main() the array i had created in address() is supposed to be gone.
The call to malloc did not create a "variable", but it allocated memory from the heap. All that is stored locally inside the function's variable y is the address of this very block of memory. This address indeed is lost when y goes away by leaving the function.
To not lose the address of the memory allocated fix the code for example like this:
#include <stdlib.h> /* for malloc() */
#include <stdio.h> /* for printf() */
void address (bool** xpp)
{
bool* y = malloc(10*sizeof(bool));
y[2] = false;
*xpp = y;
}
int main(void)
{
bool* xp;
address(&xp);
xp[0] = false;
xp[2] = false;
xp[7] = false;
printf("%d", xp[0]);
printf("%d", xp[2]);
printf("%d", xp[7]);
return 0;
}
A major flaw still with this code is, that the caller (main) and the callee (address) do not "talk" about how much memory has to be/was allocate.
Almost everything about this code is misleading.
The question is, "How can the xp pointer be valid after function address returns? But it turns out that the call to address does not modify the xp pointer.
xp is set to point to x. The call to address doesn't change that. (The call to address does end up changing what xp points to, that is, it changes x.
Although xp is not a pointer to bool, it's used as if it were, in the three assignments
xp[0] = false;
xp[2] = false;
xp[7] = false;
Although xp doesn't actually point to memory that's been allocated to hold 10 bools, wherever it does point (that is, somewhere on the stack surrounding x), there's evidently enough memory to accidentally store 10 bools without causing too much damage.
And then, having stored a few bools at wherever-it-is, trying to print those three bools back out (from wherever-it-was) happens to work.
For answers to the question of how the local storage in a function can still be accessed (though incorrectly) after the function returns -- and although this code doesn't actually end up doing that -- see my other answer.
For the record, there are other problems. The call
address(&xp);
is a type mismatch (&xpp is a pointer-to-pointer-to-pointer-to-bool, but address() accepts a pointer-to-pointer-to-bool). The line
**xpp = &y;
is a type mismatch (&y is a pointer-to-pointer-to=-bool, but **xpp is a bool). Lines like
xp[0] = false;
are a type mismatch (false is a bool, but xp[0] is a pointer-to-bool). Finally, lines like
printf("%d", xp[0]);
are a type mismatch (xp[0] is a pointer-tobool, but %d expects an int, or maybe a bool).
So I'm aware that this question is most likely asked before, but after near an hour of searching I decided to ask all the same. Pointing me to a dublicate question which has already been answered would really be appreciated.
Then, programming in basic C, I'm curious to what happens to the array-elements when changing its pointer to pointing something else? Is it safe, without first freeing it? For instance,
int main()
{
const int size = 3;
int *p_arr = malloc(size * sizeof(int));
for( int i=0; i<size; i++)
p_arr[i] = i;
int arr[size] = {0,0,0};
p_arr = arr; // safe!?
// What happens to the data previously allocated
// and stored in *p_arr? Should one first call,
// free(p_arr)
// and then reallocate ..?
}
Essentially, changing the pointer will leave the data {0,1,2} in memory. Is this okay?
Thanks alot for any help!
Nothing happens to the data, except that it becomes unreachable ("leaked") and thus the memory is forever wasted, it can't be used for anything else until your program terminates (typically).
Don't do this, it's very bad practice to leak memory.
You should free() the memory when you no longer need it.
Also, the allocation can be written:
p_arr = malloc(size * sizeof *p_arr);
to remove the duplication of the int type, and lock the size to the actual variable. This is at least somewhat safer.
The array{0,1,2}is already in the memory,but you can't get it unless you point a pointer to the head address of the array again.
Problem:
I'm rather sure my issue here is a typical misunderstanding of what's happening with pointers. I'll post what's happening in memory below, because my main goal here is to learn how to debug properly, so this is probably a long post about an otherwise trivial problem.
Here's the code, first:
/*Generate array with 500 to 1000 elements*/
/*In the calling function, I create something like 'int* x;' and pass '&x'*/
void gen_array(int** arr){
int size = rand() % (1001 - 500) + 500;
int i;
*arr = (int*) malloc(size*sizeof(int));
if (*arr == NULL){
printf("Allocation failed\n");
exit(1); //I know this is probably bad form
}
//Fill with some random numbers from 1 to 1000
for (i = 0; i < size; ++i)
*arr[i] = rand() % 1001;
}
It breaks after the first iteration in the for loop (that is, when i==1). I'm not sure if I'm allocating wrong with malloc, or assigning wrong in the loop.
Calling like this:
int* x = NULL; //Tried without '= NULL' as well
gen_array(&x);
The specific error:
Unhandled exception at 0x00265E55 in test.exe: 0xC0000005: Access violation writing location 0xCCCCCCCC.
This is after a first-chance exception at the exact same place, but they are generated at the same line of code (and on the same iteration during execution).
My attempt at analyzing:
If it helps, here's the (relevant) step-by-step from debugging on one attempt (I'm compiling in Visual Studio). I'd like to know how to use this info to help me debug in the future.
At the start of the calling function, x = 0xcccccccc (0xcccccccc=-842150451 seems to be what the compiler uses to fill uninitialized values). After set to NULL, it's equal to 0x00000000 of course.
Calling gen_array(int** arr), since I'm passing a pointer to a pointer, the local 'double pointer' is an address pointing to the NULL pointer: arr=0x0018facc {0x00000000 {???}}. (This is showing the values of what's being pointed to, since the NULL pointer is pointing to nothing, there's ???)
Call to malloc: now arr=0x0018facc {0x00e97048 {-842150451}}, so *arr is pointing to an uninitialized value
First iteration (i==0), the number generated is 588, so now arr=0x0018facc {0x00e97048 {588}} (that is, the value that *arr is pointing at is 588)
The next iteration crashes after i is increased to 1. Since it's an access violation, I would typically guess that arr+1 isn't expecting to be written to. But the violation location is at 0xCCCCCCCC, the value that this compiler uses for uninitialized data. Is this because arr+1 is technically uninitialized?
Edit: as #GrzegorzSzpetkowski advised, I tried changing *arr[i] to (*arr)[i]. It stopped breaking! but, no new random values are being created/placed into the array (which is why I'm concerned that an array isn't being created at all).
Edit 2: To #EOF's suggestion, I changed the function to return the size so I can call with something like int length = gen_array(&x);
I know I can also try returning an int* from the function, but I'd like to know what's going on specifically here in the case of passing the pointer from a calling function to be initialized.
Please check operator precence of * and []
http://en.cppreference.com/w/c/language/operator_precedence
*arr[i] = rand() % 1001;
means
*(arr[i]) = rand() % 1001;
but you want
(*arr)[i] = rand() % 1001;
Only one error is immediately obvious to me: when you initialize the array, you need (*arr)[i] =, not *arr[i] =.
Why does the following code output the same memory location everytime?
int x;
for (x = 0; x < 10; x++) {
int y = 10;
printf("%p\n", &y);
}
I thought that the memory location should change as each time the for-loop is run, the variable is new.
Yes, you are absolutely right that the memory location could change. But it doesn't have to :). In each iteration the old variable is "destroyed" and a new one is "created" at the same place. Although any decent compiler would optimize the unnecessary "actions" away
Yes, the variable is new each time around, but at the end of the block any new variables on the stack are released again.
Hence next time around the stack pointer is back exactly where it was. NB: this behaviour is common, but not guaranteed by the standards.
It's a compiler optimization. Because the local variable is going out of scope and a variable of the exact same type is about to be created, it's reusing the memory address. It's important to note that this is still a "fresh" or "new" variable as far as your program is concerned.
Compare the following code snippets and output:
for (i = 0; i < 3; i++) {
int n = 0;
printf("%p %d\n", (void *)&n, n++);
}
0x7fff56108568 0
0x7fff56108568 0
0x7fff56108568 0
for (i = 0; i < 3; i++) {
static int n = 0;
printf("%p %d\n", (void *)&n, n++);
}
0x6008f8 0
0x6008f8 1
0x6008f8 2
The scoping rules for variables only describe the scope in which you have the right to access a local variable: from the definition to the end of its block.
This rule says nothing about the moment that space is reserved for it. A common strategy for that is to reserve space for all variables that will be needed for an invocation of a function at once, at the beginning of the function.
So when execution crosses a definition of a variable, usually nothing particularly has to be done, not a single instruction. On the other hand this leaves the value of that variable to whatever was found there previously. So the importance of initializing to a known state, as you did in your example with the = 10.