I understand the intent of what you were doing, which was to return an array from ziffern_extrahieren() and copy the results of that array into the array in is_armstrong_number(). I'll point out the mistakes in what you wrote and explain how you do this in C.
int *ziffern_extrahieren(int candidate, int laenge) {
int *ziffern = malloc(...);
...
return *ziffern;
}
Here when you write return *ziffern
you are returning what ziffern
points to. The way C works, *ziffern
is equivalent to writing ziffern[0]
. So you're not returning an int *
, you're returning an int
. You'd want to write just return ziffern
.
int *ergebnis = malloc(..);
*ergebnis = ziffern_extrahieren(...);
So here, you have written is "set what ergebnis points to (ergebnis[0]
) to the return value of ziffern_extrahieren()
". But you can't do this, because the type of *ergebnis
is int
, and the return type of ziffern_extrahieren()
is int *
.
So now you see why people stopped using C!
1. memcpy
In C the way you write "take this bit of memory and copy it over to this other bit of memory" (which is the only way you can copy arrays in C apart from iterating over each element) is using memcpy()
:
void *memcpy(void *destination, void *source, size_t length);
so
int *result = ziffern_extrahieren();
memcpy(ergebnis, result, laenge * sizeof int);
Will fill ergebnis
with the result of ziffern_extrahieren()
. In full:
// Note this version doesn't return a value
int *ziffern_extrahieren(int candidate, int laenge) {
int *ziffern = malloc(laenge * sizeof int);
int power = 1;
for (i = laenge; i > 0; i--) {
ziffern[i] = (candidate % (10 * power)) / power;
power *= 10;
}
return ziffern;
}
bool is_armstrong_number(int candidate) {
int laenge = laenge_berechnen(candidate);
int *ergebnis = malloc(laenge * sizeof int);
int *result = ziffern_extrahieren(candidate, ergebnis, length)
memcpy(ergebnis, result, laenge * sizeof int);
...
return true;
}
There are two problems here still though. The first is that the above code has two memory leaks. Once memory is allocated, you need to free it. This can be fixed by adding in:
free(result);
free(ergebnis);
before the final return.
The second is more conceptual, and it's that you're allocating two different bits of memory to contain the same data and copying from one to the other. In C people generally avoid that using one of the other three approaches...
2. Allocate memory in is_armstrong_number() and pass it to ziffern_extrahieren() to be filled
This is a neater way of doing things. It's nice because the malloc() and the free() happen in the same function, so it's easier not to have a memory leak.
// Note this version doesn't return a value
void ziffern_extrahieren(int candidate, int *ziffern, int laenge) {
int power = 1;
for (i = laenge; i > 0; i--) {
ziffern[i] = (candidate % (10 * power)) / power;
power *= 10;
}
}
bool is_armstrong_number(int candidate) {
int laenge = laenge_berechnen(candidate);
int *ergebnis = malloc(laenge * sizeof int);
ziffern_extrahieren(candidate, ergebnis, length)
...
// Make sure you call free() before returning or you have a memory leak.
free(ergebnis);
return true;
}
3. Use variable length arrays
I don't know what C compiler you are using, but C99 compilers let you allocate memory on the stack without using malloc. If you're using Microsoft's compiler then you can't do this because they still don't support a 20 year old standard, but anywhere else the following is valid C!
// ziffern_extrahieren is the same as above
bool is_armstrong_number(int candidate) {
int laenge = laenge_berechnen(candidate);
int ergebnis[laenge];
ziffern_extrahieren(candidate, ergebnis, length)
...
return true;
}
This is the difference between heap allocation and stack allocation:
-
Heap allocation (asking the OS for some memory please) means you're taking over the limited memory management that the C compiler does and asking for bits of memory yourself - and freeing them later (or fogetting to).
-
Stack allocation is what you get when you write variable declarations in your code - the C compiler allocates memory for these and frees it again when it's out of scope.
4. Allocate the memory in ziffern_extrahieren() and return it to is_armstrong_number()
This is like number 2.
int *ziffern_extrahieren(int candidate, int laenge) {
int *ziffern = malloc(laenge * sizeof int);
int power = 1;
for (i = laenge; i > 0; i--) {
ziffern[i] = (candidate % (10 * power)) / power;
power *= 10;
}
return ziffern;
}
bool is_armstrong_number(int candidate) {
int laenge = laenge_berechnen(candidate);
int *ergebnis = ziffern_extrahieren(candidate, laenge);
...
free(ergebnis);
return true;
}
This is sort of nicer than 1 because the function that needs the memory creates it, which means that you can't accidentally allocate the wrong amount of memory to pass into the function. But it does mean the function where the memory is allocated is not the same function as when it needs to be freed, which makes it easier to mess up and cause memory leaks.
Question from Finn:
That is a great question! But to answer it we'll need to go on a bit of an adventure... in the nature of the C variables, the memory model underneath C variables and the secret of the equivalence of pointers and arrays.
The nature of C variables
A variable in the C is a name for a memory location. So if I run:
I get:
This tells us that
value
is a name for memory address0x7efe814c
. (This will be different on different computers and each time you run the program. It's in hexadecimal because that's the conventional way to represent memory addresses.)(What's going on with the rest of it?
(intptr_t)
is a cast which means "interpret the thing on the right as anintptr_t
".intptr_t
is a type of integer that has enough space to hold a reference to any location in memory. ThePRIxPTR
bit is the way you print that type.)The type of a variable tells us how much space it takes up in memory. This is what the sizeof operator tells us. On my computer,
sizeof(int) == 4
. That's 4 bytes, or 32 bits. (I'm using a 32 bit computer.) Soval
starts at address 0x7efe814c and takes up 4 bytes.Looking at memory locations
Underneath the idea of a varible in C is the idea of memory locations. You can't really understand variables in C without understanding the memory model behind it. Here's a program which illustrates this.
For me this program prints:
What happened?
We set up a variable containing 0x7fffffff. We then set up a byte-based access to the memory this variable was stored at using
ptr
, and printed each of the 4 bytes that made up the contents of the variable.But we got 0xff 0xff 0xff 0x7f. Why? I won't go on long diversion here, but
0x7fffffff
is stored the "wrong way around" in memory. From the way we write numbers for humans - with the largest parts on the left and the smallest parts on the right - it makes no sense to write 0x7fffffff as four bytes of 0xff 0xff 0xff 0x7f. We'd expect the four bytes to be 0x7f 0xff 0xff 0xff. But computers are weird. This is called 'little-endianness' and it's just how most computers store numbers.OK, but the real point of this example was to show how a variable in C is a name for a memory location. Both
value
and*ptr
above are names for the same memory location. You can access it as anint
, or as an array of bytes (uint8_t
s).This is really different from high level languages. I don't know Java, but in Python variables are references to instances of objects. You can't just take a number and access it as an array, they're different kinds of things. And the underlying memory model is hidden from you. In C more or less everything is just some way of accessing a memory location.
OK, so you're asking, how does this relate to how long variables are available for? Umm... Just wait while we get distracted by something else :)
Arrays and pointers
I'm going to go on a diversion to show how an array and a pointer are not different kinds of things in C. Which I hope will be useful.
In the following example I'm pulling in the
assert()
macro. This will abort the program with an error message if it's passed something that doesn't evaluate to true.Hopefully this clarifies a little bit the difference between higher level languages' ideas of arrays and C's.
Different ways memory can be allocated
So, for something a bit more directly related to your question, let's return to the issue of memory allocation. I talked before about stack and heap allocation but let's go into more detail.
And here's another version that doesn't segfault:
An attempt at answering your question
OK, so going back to your question, hopefully there's enough background now (lol) to answer it in a reasonably short way:
The contents of a variable allocated in a function are only accessible after that function has returned if the memory for the variable was allocated on the heap (using
malloc()
).Actually, I'll complicate that a bit (yay!). If you're returning a value from a function:
Then yes, that's accessible from outside the function. But it's different from returning a memory location:
This wouldn't work. So let's rephase the above:
The difference between returning a value and returning a pointer is really important in C and doesn't exist in higher level languages where you don't do manual memory allocation.
So why is that? Well, what is a pointer exactly? This gets quite Inception-y...
What is a pointer?
Output:
What is this saying then? Pointers actually are values. The value of a pointer is the address of the memory location it points to.
You can also see the addresses of each variable is 4 bytes higher than the next. So the actual layout of this program's memory looks like:
This makes it easy to define what the pointer dereferencing operator
*
does. It means "Take the value of the thing on the right of me, treat it as a memory location, and return what is in that memory location".So
*p
means "take the value of p (0x7eb7714c
), treat it as a memory location, and return what's in that memory location (0xff
)".If you're wondering how to read that line
***ppp
above, maybe it's easier with parenthesis?*(*(*ppp))
:ppp
(0x7eb77144
), treat it as a memory location, and return what's there (0x7eb77148
)*ppp
(0x7eb77148
), treat it as a memory location, and return what's there (0x7eb7714c
)*(*ppp)
(0x7eb7714c
), treat it as a memory location, and return what's there (0xff
).I've used
**p
before but I don't think I've ever used***p
in a real world program. I'm showing it for your understanding the concept, not because you'll likely ever use it!Memory allocation in C (again)
This has turned into a run through the trickiest bits of C, so I hope I've managed to make everything clear enough so far. If not, it's not your fault, it's genuinely difficult and it took me three and a half hours to get to this point in the explanation.
But I want to come back to what I said just before when trying to answer your question:
I want to refine this again now we've defined a pointer as a kind of value. We can be more precise.
All C functions that return something return values. A pointer is a value - its value is a memory location.
So the problem we encounter when we do something like this:
is that
v
is a pointer that points to a piece of memory which is no longer allocated when the function returns.When we take control of allocation, though, using
malloc()
, this is no longer a problem. We're taking control of the life-cycle of our data by allocating and freeing the memory used for it ourselves.Back to the original problem
Here is the code from solution #4 above again:
ziffern_extrahieren
returns a pointer - a memory location. Because this memory was allocated on the heap usingmalloc()
, it is still accessible after the function returns. And it'll remain accessible until free() is called.Your question, again (for the last time, I promise!)
OK, so maybe I can answer your question again:
When the memory for a variable is allocated on the stack,
When you allocate memory on the stack in a function,
There is one other kind of allocation - global allocation. Globals are not on the stack or the heap and they are never freed, they're accessible everywhere.