Sign in to follow this  
Gink

Scope question

Recommended Posts

Gink    100
This code shouldnt work correctly, right? The objects stored at the adress are destroyed when the loop is over so why does this still function as normal? b is derived class of A.
    vector<A*> vec;
    
    for(int x=0; x < 10; x++){
        B ob;
        vec.push_back(&ob);
    }
    
    vec[5]->getv();

Share this post


Link to post
Share on other sites
bytecoder    100
I wouldn't be surprised if the compiler was waiting until the end of the routine to free the memory, either that or what Invader X said.

Share this post


Link to post
Share on other sites
Gink    100
I did a test by storing addresses of classes in a vector, by storing the x value inside of them ( from 0 to 9 ). After the loop exited every vector had the last x value stored in it, which was 9.

Share this post


Link to post
Share on other sites
bytecoder    100
Well, what compiler are you using? You could probably look at the assembly generated by the compiler to see what it's doing; actually, I guess I can test it right now.

Share this post


Link to post
Share on other sites
Gink    100
Dev-cpp IDE, mingw32-c++ compiler, how do I do that? I cant even get the debugger on this working, it doesnt do anything.

Share this post


Link to post
Share on other sites
bytecoder    100
Well, you'd have to know assembly. Here's a test program I compiled:

#include <stdio.h>

int main() {
for(int i=0; i<10; ++i) {
int c = i;
printf("%d\n", c);
}

return 0;
}



It's in C, but it shouldn't matter. Anyway, this is what gcc produced for the assembly output:

.file "test.c"
.section .rodata
.LC0:
.string "%d\n"
.text
.globl main
.type main, @function
main:
.LFB3:
pushq %rbp
.LCFI0:
movq %rsp, %rbp
.LCFI1:
subq $16, %rsp
.LCFI2:
movl $0, -4(%rbp)
.L2:
cmpl $9, -4(%rbp)
jle .L5
jmp .L3
.L5:
movl -4(%rbp), %eax
movl %eax, -8(%rbp)
movl -8(%rbp), %esi
movl $.LC0, %edi
movb $0, %al
call printf
leaq -4(%rbp), %rax
incl (%rax)
jmp .L2
.L3:
movl $0, %eax
leave
ret
.LFE3:
.size main, .-main
.section .eh_frame,"a",@progbits
.Lframe1:
.long .LECIE1-.LSCIE1
.LSCIE1:
.long 0x0
.byte 0x1
.string ""
.uleb128 0x1
.sleb128 -8
.byte 0x10
.byte 0xc
.uleb128 0x7
.uleb128 0x8
.byte 0x90
.uleb128 0x1
.align 8
.LECIE1:
.LSFDE1:
.long .LEFDE1-.LASFDE1
.LASFDE1:
.long .LASFDE1-.Lframe1
.quad .LFB3
.quad .LFE3-.LFB3
.byte 0x4
.long .LCFI0-.LFB3
.byte 0xe
.uleb128 0x10
.byte 0x86
.uleb128 0x2
.byte 0x4
.long .LCFI1-.LCFI0
.byte 0xd
.uleb128 0x6
.align 8
.LEFDE1:
.section .note.GNU-stack,"",@progbits
.ident "GCC: (GNU) 3.3.5 (Debian 1:3.3.5-8ubuntu2)"


This was compiled on a 64-bit machine, and my assembly skills aren't what they used to be, but from what I can make out it looks like gcc, and by extension mingw, allocate and deallocate stack space on-demand for blocks, meaning Invader X is probably right.

Share this post


Link to post
Share on other sites
Gink    100
i know assembly, but this debugger doesnt even do anything, im used to DOS Debug, not this Dev-CPP Debug that has no effect.

Share this post


Link to post
Share on other sites
chollida1    532
The reason it works is that the vector does a copy of the object. You probably don't implement a copy cstr or operator= so it does a memberwise copy creating a new copy of your objects.

Cheers
Chris

Share this post


Link to post
Share on other sites
bytecoder    100
Quote:
Original post by chollida1
The reason it works is that the vector does a copy of the object. You probably don't implement a copy cstr or operator= so it does a memberwise copy creating a new copy of your objects.

Cheers
Chris

He's pushing the address of the objects, not the actual objects themselves.

Share this post


Link to post
Share on other sites
Helter Skelter    332
Quote:
Original post by Gink
This code shouldnt work correctly, right? The objects stored at the adress are destroyed when the loop is over so why does this still function as normal?

b is derived class of A.


vector<A*> vec;

for(int x=0; x < 10; x++){
B ob;
vec.push_back(&ob);
}

vec[5]->getv();


Technically it should work yes but that doesn't mean it's correct...


1. At the beginning of each iteration the constructor for "ob" is called. In the example this is before vec.push_back() is called.

2. At the end of each iteration the destructor of ob is called. In the example this is after vec.push_back() is called.

3. After all iterations have completed it's very likely that all elements of "vec" point to the same memory address. Changing one element changes them all.

4. After you leave the function (and especially during/after the call to another function) The memory occupied by "ob" is corrupt. If it has a vtable it's gone bye bye.


So why does it function as normal?

1. 'vec' has not been destroyed yet so it still contains all elements you added to it (in this case a pointer an object).

2. the memory pointed to by '&ob' is still valid (local - on the stack).

3. You havn't actually tried accessing any virtual functions of the objects you added to 'vec'. If you do changes are an exception will be encountered - especially if they are vtables.



Unless you are doing something that is not syntactically correct don't expect the compiler to complain about it or even alert you to it. It's really not the job of the compiler to second guess what your intentions are....that job belongs to lint++

Share this post


Link to post
Share on other sites
daerid    354
Quote:
Original post by Gink
This code shouldnt work correctly, right? The objects stored at the adress are destroyed when the loop is over so why does this still function as normal?

b is derived class of A.


vector<A*> vec;

for(int x=0; x < 10; x++){
B ob;
vec.push_back(&ob);
}

vec[5]->getv();


This behavior is undefined, which is a big no-no. All bets are off. You're using the pointer-to-member operator on a pointer that has the address of an object on the stack that is no longer in scope, and thus has been destroyed.
It may work, it may not, but by the rules of c++, you're causing undefined behavior, which can be anything from it looking like it works, to having the code kill your dog.

In short: You break the rules, you're on your own.

Share this post


Link to post
Share on other sites
Nypyren    12065
I'm guessing (from experience disassembling) that:

1. "B ob;" is allocated on the stack.

2. Stack allocation is all grouped into function scope by your compiler (all local variables in any child scope of the function) and is happening as the first instruction (SUB ESP, size_of_local_variable_area). Your "for" loop does not have a separate "SUB ESP, ###" instruction;

3. Inside the for loop, when you're getting the address-of "ob", it's evaluating to the address on the stack, which is never changing, and is valid until the end of the function where you see "ADD ESP, size_of_local_variable_area". (they use the LEAVE instruction in the 64-bit assembly above rather than ADD ESP, ###)

This explains Gink's original code and his test that filled the vector with the number "9".

Note: Whether or not "B ob" is a primitive data type or a class, it will still get allocated on the stack before the constructor is called for it. If you create a pointer (B *ob;), a pointer-sized local variable is created on the stack instead. Your non-dereferenced local variables are ALL on the stack.

Note 2: In optimized code I've seen, the compiler completely removes the "PUSH EBP; MOV EBP, ESP" stuff because it knows how to compensate for a moving ESP value, and lets it use EBP as another general purpose register (usually prioritized for a commonly-used constant, strangly enough)

P.S. That 64-bit assembly code does pretty much what I said above, except using the unoptimized EBP<-ESP (called RBP and RSP in 64-bit arch.)

daerid is correct that this behaviour is undefined, but in real world optimization, I would say that you can almost guarantee (for performance consideration) that your stack will not get cleaned up until the end of a function.

Also, AT&T syntax assembly gives me nightmares.

[Edited by - Nypyren on July 1, 2005 2:16:12 AM]

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this