• Advertisement
  • entries
    146
  • comments
    436
  • views
    198321

A Little C++ Quiz

Sign in to follow this  

549 views

Well, I've decided to do some more posts on standard C++. In particular I'll be doing some posts on the standard numerics library and certain standard containers that tend to get ignored, but that are of great value. Anyways, as a starting point, lets all enjoy this third, and possibly final, quiz on C++:

  1. Given the following piece of code, please answer the following questions:

    1. Does the first numbered line result in defined behavior? (Yes/No)

    2. What should the first numbered line do?

    3. Is the behavior of the second and third numbered lines well defined? (Yes/No)

    4. Is the fourth numbered line valid? If so, why? If not, why?
    5. Is the fifth numbered line valid? If so, why? If not, why?

    class Base {
    public:
    virtual ~Base() {}
    virtual void DoSomething() {}
    void Mutate();
    };

    class Derived : public Base {
    public:
    virtual void DoSomething() {}
    };

    void Base::Mutate() {
    new (this) Derived; // 1
    }

    void f() {
    void* v = ::operator new(sizeof(Base) + sizeof(Derived));
    Base* p = new (v) Base();
    p->DoSomething(); // 2
    p->Mutate(); // 3
    void* vp = p; // 4
    p->DoSomething(); // 5
    }


  2. What is the behavior of calling void exit(int);?

  3. Given the following piece of code, please answer the following questions:

    1. Does the first numbered line result in defined behavior?

    2. Is the behavior of the second line defined? If so, why? If not, why is the behavior
      not defined?


    struct T{};

    struct B { ~B(); };

    void h() {
    B b;
    new (&b) T; // 1
    return; // 2
    }


  4. What is the behavior of int& p = *(int*)0;? Why does it have that particular
    behavior? Is this a null reference?

  5. What is the behavior of the numbered line in the following code if I is defined as typedef int I;?
    I* p;
    ... // initialize p
    p->I::~I(); // 1


Sign in to follow this  


9 Comments


Recommended Comments

1. Does the first numbered line result in defined behavior?

  • Yes

    2. What should the first numbered line do?

  • It should reuse the storage occupied by *this.

    3. Is the behavior of the second and third numbered lines well defined?

  • Yes and no - the third line potentially writes to unallocated memory, depending on the size of Derived. If it does not, it is well defined.

    4. Is the fourth numbered line valid? If so, why? If not, why?

  • It is syntactically valid and semantically valid as per 3.8.5. Ignoring any undefined behaviour which resulted from previous lines, that is.

    5. Is the fifth numbered line valid? If so, why? If not, why?

  • It is syntactically valid, but results in undefined behaviour as the lifetime of *p has ended.


    2.What is the behavior of calling void exit(int);?

  • The exit function from cstdlib exits the program without unwinding the stack or completing execution of its current block. It has undefined behaviour if you call the function when destroying an object with static storage duration.

    3.1 Does the first numbered line result in defined behavior?

  • Yes. It is well defined as per 3.7.3.8.

    3.2 Is the behavior of the second line defined? If so, why? If not, why is the behavior not defined?

  • It is not defined as you must ensure that an object of the same type occupies the storage when the implicit call to the objects destructor takes place. This applies to objects with static and automatic storage duration (objects which have an implicit destructor call).

    4. What is the behavior of int& p = *(int*)0;? Why does it have that particular behavior? Is this a null reference?

  • This results in undefined behaviour, as it is derefencing a null pointer.

    5. What is the behavior of the numbered line in the following code if I is defined as typedef int I;?

  • It ends the lifetime of the int object, as per 12.4.15.

    Share this comment


    Link to comment
    My answers...

    1.1 No
    1.2 Calls the derived constructor to constuct a derived object at the address pointed to by this
    1.3 line 2, yes... line 3, I'm gonna go with no
    1.4 No, no implicate casts to void*
    1.5 I've a feeling, no.. but I'm not sure why so can't explain it [oh]

    2. Program exits; stack objects are destructed, all functions registered to run at exit are run, global objects are destructed

    3.1 No
    3.2 I'm gonna go for no, stack corruption would be my guess as to why things could go wrong

    4. I don't know, but I did see something about this relating to an engine recently [grin]
    5. No idea, but something tells me it shouldn't work, crash of some sort?

    I've a feeling these will be good for comady value only [grin]

    Share this comment


    Link to comment
    1.1 - Doubt it if the class was initially created on the stack, though if it was allocated on the heap it seems like it might behave "as expected".

    1.2 - It should construct an instance of the derived class "on top of" the base class - essentially converting the base class to a derived class.

    1.3 - The second line is well defined - it should call Base::DoSomething. I think the third line is okay as well - since the object was allocated on the heap (as mentioned above).

    1.4 - Yep, implicit cast to a void* is fine.

    1.5 - Will throw a compile-time error - type void has no defined functions.

    2.0 - No idea.

    3.1 - I don't think so - its attempting to dynamically allocate on the stack.

    3.2 - Probably something to do with stack unwinding, but I've no idea.

    4.0 - Err, it attempts to dereference a null pointer, which should throw an access violation. I think :X

    5.0 - Will that even compile? [wow]

    Share this comment


    Link to comment
    Here's my stab:

    1.1. I'm going with not defined. It will probably "work" as expected in this instance but something more complicated could cause memory leaks or worse because you are destroying state in a way the compiler can't deal with. If the target was a POD I'd probably answer differently.

    1.2. Overwrite the memory pointed to by 'this' with a freshly constructed 'Derived' object.

    1.3. yes/yes. The compiler will insert the appropriate call with the appropriate virtualness. The actual behavior of the called code is a different question, see 1.5.

    1.4. Sure. You can convert to void* as long as you keep decorations such as const.

    1.5. The call itself is valid, the behavior is subject to the answer to 1.1. Since I answered undefined there it must still be undefined.


    2. Shutdown the app and return it's parameter to the OS. I don't remember if global objects get destructed or the stack gets unwound (I would guess not for the latter). I think any atexit registrations get called.


    3.1. This is basically the same as 1.x. with the only twist that now you're slamming a POD on top of a non-POD. I'm still going with undefined.

    3.2. return will cause b to get destructed and since b is in an undefined state...


    4. p is a null reference. The assignment itself won't cause anything bad to happen because assigning a reference doesn't actually cause a dereference although the syntax looks like it should. Any attempt to use p after that is undefined (likely it will crash).


    5. Yes, this does actually compile (I was a bit surprised as well). I'm guessing this is basically a hack to allow templates to work without special-casing primitive types. Since primitives don't have destructors nothing happens.


    Share this comment


    Link to comment
    2. I was a bit surprised to discover the output of this
    #include <iostream>
    

    class foo
    {
    public:
    ~foo() {std::cout << "foo's destructor called\n"; }
    };


    class bar
    {
    public:
    ~bar() {std::cout << "bar's destructor called\n"; }
    };


    int main()
    {
    foo a;
    static bar b;

    exit(0);
    }





    is:
    bar's destructor called


    Apparently exit() only calls the destructors on static objects - though I have no idea as to what order the objects are destructed, and whether this happens before or after functions registered with atexit() are called.

    Also, IIRC a function registered with atexit() that itself calls exit() is undefined behavior, possibly leading to endless recursion (depending on implementation).

    Share this comment


    Link to comment
    Just a guess...

    1.1 Valid. It calls placement new operator.
    1.2 Using placement new it will construct instance Derived on top of current instance of Base class.
    1.3 Second line yes. It will call Base::doSomething()
    Third line yes. This will create Derived in the memory allocated for Base.
    1.4 Yes. Implicit cast to void is OK.
    1.5 This will probably fail, since p is pointer to the Base but after call to the Mutate it points instance of Derived and it will try to acces wrong vtable entry (not entirely sure about this...)

    2. It will call atexit() function which will in turn call all function registered with it. Then memory is freed (only globally allocated objects), all files closed etc. and program exits with given return code.

    3.1 I think it will work. Even empty classes have size so it should construct new T over the top of the instance of B.
    3.2 This should call destructor of B, but since there's T on that place now it may fail. But... I tested it on VC2005 and it worked, so I'm not sure.

    4 I have no idea what it does. Maybe it will point to the int at 0x00000000. If it does, attempting to read it will crash program. It certainly is not null reference, since null references do not exist.

    5. Nothing? It will call destructor for built-in type which does nothing.

    Share this comment


    Link to comment
    Will F; ther's a simple explanation. exit(0) never returns, and it's main's responsibility to call a's destructor when it returns.

    Share this comment


    Link to comment
    Yeah, I asked Washu about it on #gamedev.
    My understanding of exit() and how it pertains to scope was flawed.

    Share this comment


    Link to comment

    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

    • Advertisement