Page 1 of 1

C++ Con/Destructor implementation question

Posted: Wed Jun 03, 2009 7:57 am
by gravaera
When writing aKernel in C++, I have a few questions about implementation of C++ core features that need programmer definition.

I know that new, delete, and several other key features need redefining. But do I also need to implement my own malloc() function? I know that memcpy() memset(), and memcmp(), etc need to be redefined without Standard Lib support.

Also: Since I've looked all over, and found nothing on this:

I've read that the compiler calls constructors and destructors at the end of the block for which the object was defined. Yet, in all of the tutorials i've seen so far, they talk about an implementation of the functions _main() and _atexit() that call destructos upon the opening and closing of the PROGRAM, for GLOBAL vars.

Do these also get called at the end of blacks? for example:

Code: Select all

class foo
{
    foo(){}; //constructor;
    ~foo(){}; //destructor;
    void bar(void){}
};

void func1(){
    foo foo_obj;
}

int main(void)
{
    func1();
    //Does the destructor for the foo object initialized in here get called?
    {
        foo foo_obj2
        //This var was initialized in its own block. Will the destructor be called?
    }
};
Will the destructors for objects that are not declared directly within main, or as global vars, be called by the program if I only implement these tow functions, _main(), and _atexit()?

Thanks in advance. I understand that this may have been loked at elsewhere, but I've searched, and found nothing comprehensible.

Re: C++ Con/Destructor implementation question

Posted: Wed Jun 03, 2009 8:18 am
by yemista
I know that new, delete, and several other key features need redefining. But do I also need to implement my own malloc() function? I know that memcpy() memset(), and memcmp(), etc need to be redefined without Standard Lib support.
You need to implement everything that you ever plan on using.

Re: C++ Con/Destructor implementation question

Posted: Wed Jun 03, 2009 8:44 am
by gravaera
Thanks, but what about the constructor question? How is it that the _main() function can be called at the opening of the whole program, yet be able to call the constructors for elements that will be defined later on, in separate functions?

How does the _atexit() function implememnt the destructors for objects that are defined as non global, and not included in the main() function's scope?

Could someone knowledgable just explain that to me? I'd honestly love to know.

Re: C++ Con/Destructor implementation question

Posted: Wed Jun 03, 2009 9:03 am
by pcmattman
We have the C++ page on our wiki, which should answer all of your implementation questions.

As for the constructor/destructor thing - in your main() function, foo is declared within a new scope block (the {}) and so its destructor will be called when it goes out of scope (when a code path leaves the scope block).

Re: C++ Con/Destructor implementation question

Posted: Wed Jun 03, 2009 9:06 am
by JamesM
Hi,

Code: Select all

class foo
{
    foo(){}; //constructor;
    ~foo(){}; //destructor;
    void bar(void){}
};

void func1(){
    foo foo_obj;
}

int main(void)
{
    func1();
    //Does the destructor for the foo object initialized in here get called?
    {
        foo foo_obj2
        //This var was initialized in its own block. Will the destructor be called?
    }
};
I've attempted to provide an ascii-art analysis of the code flow below:

Code: Select all

--> enter main()
  --> enter func1()
      call constructor foo::foo() for object foo_obj, created on the stack.
      call destructor  foo::~foo() for object foo_obj.
  <-- leave func1()

  --> start scoping block {
     call constructor foo:foo() for object foo_obj2, created on the stack.
     call destructor foo::~foo() for object foo_obj2.
  <-- leave scoping block }
<-- leave main()
Both foo_obj and foo_obj2 are local variables. They are thus created on the stack, and exist when their immediate parent scope is entered. So, foo_obj is constructed when func1() is entered, and destroyed when func1() is exited.

foo_obj2's immediate parent scope is the explicit scoping block {...}, so when the first curly brace is passed, the constructor is called. Again when the } is passed in the logical execution flow the destructor is called.

_atexit() doesn't do anything for non-global variables. It calls the destructors for global variables only.

Hope this helps,

James

Re: C++ Con/Destructor implementation question

Posted: Wed Jun 03, 2009 9:44 am
by Creature
JamesM and everyone here is right. I would like to add to what is already stated though:

1) You indeed do need to write everything yourself, including malloc, free, memset and anything else you would want to use (unless you take someone else's code and drop it in your OS). Your very first form of memory allocation is usually an integer, starting at value 'end_of_your_kernel' that gets returned and then gets 'size_of_memory_you_wish_to_allocate' added to it. However, since you will eventually run out of memory, you will need something that manages blocks of memory and allocations internally, this is what is commonly referred to as the heap (see JamesM's tutorials or the Writing a memory manager article on the wiki for an explanation).

2) As James said, constructors and destructors of objects in C++ are already called inside a normal function, BUT this does not include global variables (as stated on the wiki). Global variables can be created, but they will not be initialized, so in a bare C++ OS with no actual support yet, the following will happen:

Code: Select all

/* This variable will probably not be 5000 when you enter your entry point. */
int MyGlobalVar = 5000;

/* Random function. */
void foo()
{
   /* This variable is locally declared and allocated on the stack, as JamesM said. It should have value '2000' as expected. */
   int SomeVar = 2000;
}

int main()
{
   foo();
}
The global variable will most likely not have the expected value, it WILL exist however, so a first solution to your problem could be something similar to the following:

Code: Select all

int MyGlobalVar;

void InitializeGlobalVars()
{
   MyGlobalVar = 2000;
   /* ... */
}

int main()
{
   InitializeGlobalVars();
}
However, this is redundant, and forces you to initialize every global variable similarly in that function. The second (and in my opinion, the best) solution is to write a loop that does all the initialization. The wiki page 'C PlusPlus bare bones' explains how to do this in the 'loader.s' section. You can loop the constructors in either your C++ code or Assembly (Assembly is probably the best choice). The thing about this solution is: you add the code once, you never look at it again (unless it's causing problems, of course).

There's also discussion about whether it's needed to loop the destructors as well. Some say you should, others share the opinion "why would I call destructors of global objects in my OS if my OS is shutting down anyway?". Here's my constructor code, it might serve as an example if you need it:

Code: Select all

; Note that these are declared in the linker script with the same names.
[EXTERN LdConstrStart]
[EXTERN LdConstrEnd]

ExecConstr:
	push ebx					; Preserve EBX contents.

	mov ebx, LdConstrStart		; Load the first constructor.
	jmp .Compare				; Jump to the compare label.
	
	.Body:						; This is the body, it calls the constructor and increments the 'pointer'.
		call [ebx]				; Call the constructor.
		add ebx, 4				; Move to the next one.
	
	.Compare:					; This checks if we've reached the end of the constructor 'list'.
		cmp ebx, LdConstrEnd	; Check if the constructor inside ebx is the last constructor.
		jb .Body				; If it isn't, go back to Body.
		
	pop ebx						; Return EBX to its old state.
	ret							; Return, we're done here.
Hope this 'summary' was of any help to you (others feel free to correct my mistakes).

Re: C++ Con/Destructor implementation question

Posted: Wed Jun 03, 2009 9:51 am
by skyking
There's not much magical with the constructor and destructor. They are ordinary functions, maybe with some special ABI twitches. They will be generated by the compiler as possibly needed. Every call that is to be done after main has been entered (but before it returns or exit is called) will have to be generated by the compiler as well.

The rest of the story (what happens before main, what happens after main and what is needed to get static objects in function scope) is told in the wiki IIRC.

Re: C++ Con/Destructor implementation question

Posted: Wed Jun 03, 2009 9:57 am
by Combuster
Moved to general programming

Re: C++ Con/Destructor implementation question

Posted: Wed Jun 03, 2009 1:13 pm
by gravaera
Zomg...I'm so sorry Combuster. I keep placing my topics in the wrong sections. I'll try to be much more careful now. My deepest apologies for making you have to move two of my topics. :oops:

Thank you SO much all of you, and I thank especially Creature, and JamesM for so clearly explaining everything to me.

Creature, your Assembly is so clean and simple, that I, who have never learned any assembly before, was able to understand and follow it. That is a big deal. And that also means that you've helped me understand some basic concepts having to do with the CPU stack, and how it correlates to the registers, and all that.

OMG...this forum is AWESOME!!! I can't ever remember that last time I found such a programming gold mine! LOL

Re: C++ Con/Destructor implementation question

Posted: Thu Jun 04, 2009 10:34 am
by JamesM
Creature wrote: 2) As James said, constructors and destructors of objects in C++ are already called inside a normal function, BUT this does not include global variables (as stated on the wiki). Global variables can be created, but they will not be initialized, so in a bare C++ OS with no actual support yet, the following will happen:

...

Hope this 'summary' was of any help to you (others feel free to correct my mistakes).
I'm sorry, but you're wrong. Simple POD (plain ol' data) types such as integers, pointers etc are loaded into a program file's initialised data section (.data), as opposed to the zero or uninitialised data section (.bss).

An (ELF) loader will ensure that the correct value is loaded into the correct address before program execution is started, so the variable can be assumed to be initialised.

Where it can't, is where the datatype is non-POD - i.e. a class instance. Then, a special function is created automatically by the compiler to perform the initialisation and deinitialisation (usually called something like __GLOBAL__foobar()). A pointer to these functions are put into the special sections ".ctors" and ".dtors" respectively - one then iterates through these on kernel bootup to execute global constructors/destructors.

Cheers,

James

Re: C++ Con/Destructor implementation question

Posted: Thu Jun 04, 2009 2:13 pm
by Creature
JamesM wrote:
Creature wrote: 2) As James said, constructors and destructors of objects in C++ are already called inside a normal function, BUT this does not include global variables (as stated on the wiki). Global variables can be created, but they will not be initialized, so in a bare C++ OS with no actual support yet, the following will happen:

...

Hope this 'summary' was of any help to you (others feel free to correct my mistakes).
I'm sorry, but you're wrong. Simple POD (plain ol' data) types such as integers, pointers etc are loaded into a program file's initialised data section (.data), as opposed to the zero or uninitialised data section (.bss).

An (ELF) loader will ensure that the correct value is loaded into the correct address before program execution is started, so the variable can be assumed to be initialised.

Where it can't, is where the datatype is non-POD - i.e. a class instance. Then, a special function is created automatically by the compiler to perform the initialisation and deinitialisation (usually called something like __GLOBAL__foobar()). A pointer to these functions are put into the special sections ".ctors" and ".dtors" respectively - one then iterates through these on kernel bootup to execute global constructors/destructors.

Cheers,

James
I see, well I had only really tested it with custom objects, that's probably why I assumed wrongly that built-in types also needed to be initialized by the kernel itself. Sorry for giving the wrong information and thanks for correcting me.