Knowledge base

IT is a great and exciting world to be in.
All who have passion for it will understand why we have created these pages.
Here we share our technical knowledge with you.
ProgrammingAICloudManagementMobileDB & AnalysisSafetyOtherArchitectureTips

OOP #2 - Null and pointer on a pointer

Libor Bešenyi (Solution Architect)

In the previous section, we have shown how program data that are stored on it can look. In order for the program to know where the data of one class begin and the data of another class end, it needs to have one more "table" to contain these addresses.

Thus, we have a single string class:

public class Class
{
    public string Text;
}

We create three instances:

private void button1_Click(object sender, EventArgs e)
{
    Class class1 = new Class();
    class1.Text = "A";

    Class class2 = new Class();
    class1.Text = "B";

    Class class3 = new Class();
    class1.Text = "C";
}

In the memory, it looks as follows: 

0

1

2

3

4

5

6

8

8

9

10

11

12

13

14

A

 

 

   

B

   

 

 

C

     

 

The program will create one more table, where the individual pointers also allocated addresses (of course, the terminology is not the same as in the programming language - for better understanding, I recommend to consult assembler or some Introduction to compilers – however, this is not a condition for mastering OOP).

class1 0
class2 5
class3 10
(reserved)  
(reserved)  

This is what the table of pointers could look like. If we want to organize it in the memory on a sequence, we can imagine that space is reserved for pointers and then other space is reserved for the data: 

0

1

2

3

4

5

6

8

8

9

10

11

12

13

14

15

16

17

18

19

5

 

10

15 

-

-

A

   

 

 

B

     

 

C

 

     

As you can notice, in the first part we have reserved space for pointers, while the content of this space in the memory is the address for another space in the memory. Therefore, to follow up on the previous chapter (dealing with the use of references in the method), let us stop initially at non-existent objects. What happens if we don’t create an instance?

private void button1_Click(object sender, EventArgs e)
{
    Class class1;
    DoSomething(class1);
}

public void DoSomething(Class class)
{
}

In this case, the compiler does not let the code be translated (Delphi would allow for it):

Use of unassigned local variable 'class1'

But it is not the only case when the object is not created – let’s note that the compiler can detect this error only in a place where the variable occurs. In run-time (running program), however, the compiler cannot detect the conflicts arising from the program, and these conditions must be treated by the programmer. To look at these errors, we will not create a new instance, but we will define the object as empty (NULL / nil):

private void button1_Click(object sender, EventArgs e)
{
    Class class1 = null;
    DoSomething(class1);
}

public void DoSomething(Class class)
{
}

Now we actually made the program translatable (design-time, which is in control of the compiler). Run-time is then the responsibility of the programmer. In this case - even if the instance is empty, nothing happens in the run-time, because the object is not used anywhere. Our memory might look something like this:

0

1

2

3

4

5

6

8

8

9

10

11

12

13

14

15

16

17

18

...

 

-

-

-

-

- -

 -

-

-

- - -

-

- - ...

The program registered a new pointer, but did not reserve any space for the object, because it is non-existent. If we wanted to use it, the program would die (when first used in the run-time). Let's edit the method to display the contents of the variable Text of the given class.

As we can see, the program can be translated, although obviously it contains a logical error. It is not a compiler’s error, but a programmer’s error (because sometimes similar condition is desirable). So what happens when I start the button? We get an exception (error) of the program:

Object reference not set to an instance of an object.

This error deals with the fact that we are trying to access the data part of the object that does not exist. Let's look back at the table in memory, what it would look like if the object existed:

Class class1 = new Class();
class1.Text = "bla";

0

1

2

3

4

5

6

8

8

9

10

11

12

13

14

15

16

17

18

...

5

 

-

-

-

B L a

 

  - - - -

-

- - ...

Data part is therefore the right one. To see the text from the data field in the source code, the "right" part is actually after the object:

class.Text

Basically, up to this place of performance of the code, the program died. The calling of the pointer itself did not (as it exists on the left side - the compiler took care of it). If we therefore perform an operation on the pointer itself, in case it doesn’t exist, nothing happens until we try to approach its data (an area which it should link to):

private void button1_Click(object sender, EventArgs e)
{
    Class class1 = null;
    DoSomething(class1);
}

public void DoSomething(Class class)
{
    class = class;
}

In this case, the error does not occur, because we only work with the pointer, and it exists in the table of references (it only has invalid value) and therefore nothing happens. Therefore, we can modify our procedure to see if an object was created. If not, there is nothing we can do about it. To determine whether the object exists, we can compare the value of the pointer to NULL value:

private void button1_Click(object sender, EventArgs e)
{
    Class class1 = null;
    DoSomething(class1); // Does nothing

    Class class2 = new Class();
    class2.Text = "bob";
    DoSomething(class2); // Shows text
}

public void DoSomething(Class class)
{
    if (class != null)
        MessageBox.Show(class.Text);
}

In the previous chapter we have shown that if you change the data in the object sent through a pointer, it will change upon its return because the pointer sends an address to the procedure. But what happens when we change the pointer?

private void button1_Click(object sender, EventArgs e)
{
    Class class = new Class();
    class.Text = "bob";           
    DoSomething(class);
}

public void DoSomething(Class class)
{
    MessageBox.Show(class.Text);           
    class = null;
    MessageBox.Show(class.Text);   // DIES
}

Assuming the pointer is a number sent to the method, then on the first line it still shows the same location where the object exists (hence the message will be displayed). However, if we change the address, the program dies (we showed you why).

In the memory, it looks as follows:

0

1

2

3

4

5

6

8

8

9

10

11

12

13

14

15

16

17

18

...

5

 

5

-

-

b

L a

 

 

-

- - -

-

-

- - ...

There is a temporary new pointer (pointer table on the left), which points to the same location in the memory (hence, we can change the data, because the pointer points to a common address as the one "before" it), where the object is created. In the second step we then assign NULL value to the temporary pointer, the table changes as follows:

0

1

2

3

4

5

6

8

8

9

10

11

12

13

14

15

16

17

18

...

5

 

 

-

-

b

L a

 

 

-

- - -

-

-

- - ...

The pointer has an invalid value - but it doesn’t change the fact that the object still exists! If we modify the program to ignore display by assigning NULL value, this will work:

private void button1_Click(object sender, EventArgs e)
{
    Class class = new Class();
    class.Text = "bob";      
    DoSomething(class);
    MessageBox.Show(class.Text);   // Works again
}

public void DoSomething(Class class)
{
    MessageBox.Show(class.Text);
    class = null;
}

Why does it work? Because once the procedure DoSomething () ends and we return back, the original pointer (from address 0) still refers to the address of the object in the data section (5)! But what does the reference cause in this case? Pointer to a pointer? It causes that the address of the object does not reach the method, it is the address of the pointer! Thus, the number from the left:

private void button1_Click(object sender, EventArgs e)
{
    Class class = new Class();
    class.Text = "bob";
    DoSomething(ref class);
    MessageBox.Show(class.Text);   // DIES
}

public void DoSomething(ref Class class)
{
    MessageBox.Show(class.Text);
    class = null;
}

What actually happened in the memory? New pointer was not created in the method; the original was used:

0

1

2

3

4

5

6

8

8

9

10

11

12

13

14

15

16

17

18

...

5

 

-

-

-

b

L a

 

 

-

- - -

-

-

- - ...

If we assigned NULL value to it, even after the return from the method, the original pointer points to invalid value (we can notice that the object in the data part exists, but we lost information about it - we'll say more in the part about memory leaks and garbage collector later).

0

1

2

3

4

5

6

8

8

9

10

11

12

13

14

15

16

17

18

...

 

-

-

-

b

L a

 

 

-

- - -

-

-

- - ...

Thus, the use of REF has different justification than in the pointer arithmetic and if we work in a team, we need to write programs so as not to unduly mislead (in case anyone else wants to modify the method, REF being present there, they must refactor the code, to find out if the pointer changes - and if it's irrelevant, it’s unnecessary loss of time).

Finally, an example where it makes sense to use REF:

private void button1_Click(object sender, EventArgs e)
{
    Class class = null;
    DoSomething(ref class);
    MessageBox.Show(class.Text);

    Class class2 = new Class();
    class2.Text = "BMW";
    DoSomething(ref class2);
    MessageBox.Show(class2.Text);
}

public void DoSomething(ref Class class)
{
    if (class == null)
    {
        class = new Class();
        class.Text = "Auto";
    } // if

    MessageBox.Show(class.Text);
}

 

Find out more

Programming

Quick start

1
Contact
Contact us for a free consultation on your business needs!
2
Analysis
After the discussion we will perform mapping of the processes and analyse the current state.
3
Proposal
You will receive variety of scenarios to choose from discribing different ways how to solve your issue.
Contact us