5

I come from many years of development in Java, and now that I want switch to C++ I have hard times understanding the memory management system.

Let me explain the situation with a small example:

From my understanding, you can allocate space either on the stack or on the heap. The first is done by declaring a variable like this:

 int a[5]

or

int size = 10;
int a[size]

On the contrary, if you want to allocate memory on the heap, then you can do it using the "new" command. For example like:

int *a = new int[10]; (notice that I haven't tried all the code, so the syntax might be wrong)

One difference between the two is that if it is allocated on the stack when the function is finished then the space is automatically deallocated, while on the other case we must explicitly deallocate it with delete().

Now, suppose I have a class like this:

class A {
  const int *elements[10];

  public void method(const int** elements) {
    int subarray[10];
    //do something
    elements[0] = subarray;
  }
}

Now, I have few questions:

  1. in this case, subarray is allocated on the stack. Why after the function method has finished, if I look on elements[0] I still see the data of subarray? Has the compiler translated the first allocation in a heap allocation (and in this case, is this a good practice)?
  2. if I declare subarray as "const", then the compiler does not let me assign it to elements. Why not? I thought that the const only concerns the inability to change the pointer, but nothing else.
  3. (this is probably quite stupid) suppose I want to allocate "elements" not with a fixed 10 elements, but with a parameter that comes from the constructor. Is it still possible to allocate it in the stack, or the constructor will always allocate it in the heap?

Sorry for such questions (that might look silly to a expert C programmer), but the memory management system of C++ is VERY different from Java, and I want to avoid leakings or slow code. Many thanks in advance!

Chad
  • 18,706
  • 4
  • 46
  • 63
jrbn
  • 303
  • 2
  • 6
  • 2
    Please note that you don't have to use `new` explicitly, and it's even considered bad practice by some. Smart pointers combined with `make_shared` (or `make_unique`) provide more convenience and safety; for arrays of objects, there's `std::vector` and `std::array`. – dyp Oct 01 '13 at 16:59
  • `int size = 10;` must be `const int size = 10;` or `constexpr int size = 10;` and there's a colon missing after `public`. – dyp Oct 01 '13 at 17:12

6 Answers6

2

A) No, the compiler has not translated it and you're not venturing into undefined behavior. To try to find some parallels to a Java developer, think about your function arguments. When you do:

int a = 4;
obj.foo(a);

what happens to a when it's passed to the method foo? A copy is made, it is added to the stack frame, and then when the function returns the frame is now used for other purposes. You can think of local stack variables to be a continuation of the arguments, since they're typically treated similarly, barring calling convention. I think reading more about how the stack (the language-agnostic stack) works can illuminate further on the issue.

B) You can mark the pointer const, or you can mark the stuff it points to const.

int b = 3
const int * const ptr = &b;
^            ^
|            |- this const marks the ptr itself const
| - this const marks the stuff ptr points to const

C) It is possible to allocate it on the stack in some C++ standards, but not in others.

yan
  • 20,644
  • 3
  • 38
  • 48
  • 1
    Maybe this is just because I'm still learning C++ myself, but didn't you get the meanings of the `const`s there mixed up? I'm referencing [this question](http://stackoverflow.com/questions/1143262/what-is-the-difference-between-const-int-const-int-const-int-const) (and [this comment](http://stackoverflow.com/questions/1143262/what-is-the-difference-between-const-int-const-int-const-int-const#comment962682_1143272)) for info. – ajp15243 Oct 01 '13 at 17:11
  • @ajp15243 You're right. Preferring to put the `const` after the type clears things up IMO: `int const* const ptr`. Here, the first const refers to the `int`, whereas the second refers to the pointer. – dyp Oct 01 '13 at 17:14
  • 1
    @ajp15243: Correct. The first `const` marks the content of the pointed memory to be const, while the second makes the pointer value itself `const`. I'd suggest to write ptrs like this: ` const * const = ;` i.e. `int const * const ptr = &b;` where each const applies to the left. The first to `int` (pointed value) the second to `*` (the pointer). – Pixelchemist Oct 01 '13 at 17:17
  • @Pixelchemist That suggestion also makes the answer in the question to which I linked sensible: "Read it backwards". – ajp15243 Oct 01 '13 at 17:18
  • yan, what do you mean with your statement for **C)**? Arrays of runtime bounds are only allowed in C++1y / some TR to it. – dyp Oct 01 '13 at 17:31
  • `C` makes no sense the standard does not mention the stack at all. – Shafik Yaghmour Oct 01 '13 at 17:33
2

a) in this case, subarray is allocated on the stack. Why after the function method has finished, if I look on elements[0] I still see the data of subarray? Has the compiler translated the first allocation in a heap allocation (and in this case, is this a good practice)?

It's called "undefined behavior" and anything can happen. In this case, the values that subarray held are still there, incidentally, probably because you access that memory immediately after the function returns. But your compiler could also zero-out those values before returning. Your compiler could also send fire-spewing dragons to your home. Anything can happen in "undefined behavior"-land.

b) if I declare subarray as "const", then the compiler does not let me assign it to elements. Why not? I thought that the const only concerns the inability to change the pointer, but nothing else.

This is a rather unfortunate quirk of the language. Consider

const int * p1; // 1
int const * p2; // 2
int * const p3; // 3
int * p4;       // 4
int const * const p5; // 5

This is all valid C++ syntax. 1 says that we have a mutable pointer to a const int. 2 says the same as 1 (this is the quirk). 3 says that we have a const pointer to a mutable int. 4 says that we have a plain old mutable pointer to a mutable int. 5 says that we have a const pointer to a const int. The rule is roughly this: Read const from right-to-left, except for the very last const, which can either be on the right or on the left.

c) suppose I want to allocate "elements" not with a fixed 10 elements, but with a parameter that comes from the constructor. Is it still possible to allocate it in the stack, or the constructor will always allocate it in the heap?

If you need dynamic allocation, then this will usually be on the heap, but the notion of stack and heap is implementation-dependent (i.e. whatever your compiler vendor does).

Lastly, if you have a Java background, then you'll need to consider ownership of memory. For example, in your method void A::method(const int**), you point your pointers to locally created memory, while that memory goes away after the method returns. Your pointers now point to memory that nobody owns. It would be better to actually copy that memory into a new area (for example, a data member of the class A), and then let your pointers point to that piece of memory. Also, while C++ can do pointers, it would be wise to avoid them at all costs. For example, strive to use references instead of pointers when possible and appropriate, and use the std::vector class for arbitrary sized arrays. This class'll also take care of the ownership problem, as assigning a vector to another vector will actually copy all the elements from the one to the other (except now with rvalue references, but forget that for the moment). Some people regard a "naked" new/delete as bad programming practice.

rwols
  • 2,968
  • 2
  • 19
  • 26
  • "If you need dynamic allocation, then this will always be on the heap. That's just how a stack works;" It is possible to allocate a dynamic amount of memory on the stack, see C's VLAs and C++14/TR `std::dynarray` and arrays of runtime bound. – dyp Oct 01 '13 at 17:25
  • http://stackoverflow.com/questions/19111028/c14-stddynarray-vs-stdvector I guess you're right... However at runtime the deltas of the stack are still fixed, since the size of `std::dynarray` cannot be altered after construction. – rwols Oct 01 '13 at 17:32
  • You can define two `std::dynarray`s. The second one won't have a fixed offset to neither BP nor SP. – dyp Oct 01 '13 at 17:35
1

One of the major differences between Java and C/C++ is explicit Undefined Behavior (UB). The existence of UB is a major source of performance for C/C++. The difference between UB and "Not allowed" is that UB is unchecked, so anything can happen. In practice, when a C/C++ compiler compiles code that triggers UB the compiler will do whatever produces the most performant code.

Most of the time that means "no code" because you can't get any faster than that, but sometimes there are more aggressive optimizations that come from conclusions of UB, e.g a pointer that was dereferenced cannot be NULL (because that would be UB), so a check for NULL later should always be false, therefore the compiler will rightfully decide that the check can be left away.

Since it is often also hard for the compiler to identify UB (and not required by the standard), it truly is correct that "anything can happen".

1) According to the standard it is UB to dereference a pointer to an automatic variable after you left the scope. Why does that work? Because the data still is there in the location you left it. Until the next function call overwrites it. Think of it like driving a car after you sold it.

2) There are actually two consts possible in a pointer:

int * a;                        // Non const pointer to non const data
int const * b;                  // Non const pointer to const data
int * const c = &someint;       // Const pointer to non const data
int const * const d = &someint; // Const pointer to const data

The const before the * refers to the data and the const after the * refers to the pointer itself.

3) Not a stupid question. In C it is legal to allocate an array on the stack with dynamic size, but in C++ it is not. This is because in C there is no need to call constructors and destructors. This is a hard problem in C++ and was discussed for the latest C++11 standard but it was decided that it will stay the way it was: It's not part of the standard.

So why does it work sometimes? Well, it works in GCC. This is a non-standard compiler extension of GCC. I suspect that they simply use the same code for C and C++ and they "left it in there". You can turn this off whith that GCC switch that makes it behave in a standard way.

Fozi
  • 4,973
  • 1
  • 32
  • 56
  • @DyP Note how I did _not_ write "triggering UB is a major source of performance". Also, UB does not mean "Not covered by the standard". It means that the standard explicitly says that something is UB. There is no such thing in the Java Language Specification. I read it. – Fozi Oct 01 '13 at 18:55
  • Alright, let's clean up the comments. I'm still not sure what you mean with "In practice, when a C/C++ compiler compiles code that triggers UB the compiler will do whatever produces the most performant code." The example that follows suggests something like "In practice, for code that can cause UB (e.g. depending on run-time values), the compiler will not check but produce the most performant code." – dyp Oct 01 '13 at 19:27
  • @DyP Please don't make me open a nitpicker's corner. Don't forget who the target audience of this answer is. If you are interested in reading more about UB, I recommend reading the triology starting here: http://blog.regehr.org/archives/213 – Fozi Oct 01 '13 at 19:32
  • Sorry, my `-pedantic` switch broke off in its *ON* position a long time ago ;) I've already upvoted your answer, but I'm striving to be as precise and clear as possible in my own answers, therefore appreciating even really minor issues such as this. – dyp Oct 01 '13 at 19:51
0

The standard does not talk about the stack or the heap, in this case your array has automatic storage which in most modern systems will be on the stack. It is just plain undefined behavior to keep a pointer to an automatic object once you have exited the scope and then access it. The draft C++ standard in section 3.7.3 paragraph 1 says(emphasis mine):

Block-scope variables explicitly declared register or not explicitly declared static or extern have automatic storage duration. The storage for these entities lasts until the block in which they are created exits.

Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
0

a) You see it because the stack space for it has not yet been reclaimed. This memory is subject to being overwritten as the stack grows and shrinks. Do not do this, results are undefined!

b) subarray is a integer array, not a pointer. If it is const, you cannot assign to it.

c) Not a stupid question at all. You can do it with a placement new. It is also possible to use a variable to dimension an array on the stack.

bspikol
  • 96
  • 6
0

re a): When the function returns the data is still where you put it, on the stack. But it is undefined behavior to access it there, and that storage will be reused almost immediately. It will certainly be reused upon the next call to any function. That's inherent in the way the stack is used.

ScottMcP-MVP
  • 10,337
  • 2
  • 15
  • 15