How are C + + objects distributed in memory

Function reference returns analysis memory

Here we allow you to connect the code used in the previous copy structure. You can review the previous text for easy understanding
Copy construction in C + +

class Object
{
	int value;
public:
	Object()
	{
		cout << "Object::Object" << this << endl;
	}
	Object(int x = 0) :value(x)
	{
		cout << "Object::Object" << this << endl;
	}
	~Object()
	{
		cout << "Objecet::~Object" << this << endl;
	}

	Object(Object& obj) :value(obj.value)
	{
		cout << "Copy Create" << this << endl;
	}
	void SetValue(int x) { value = x; }
	int GetValue() const { return value; }
};
Object fun(Object obj)
{
	int val = obj.GetValue();
	Object obja(val);
	return obja;
}

int main()
{
	Object objx(0);
	Object objy(0);
	objy = fun(objx);
	return 0;
}

We modified the above code to build fewer objects

Object::int& Value() {return value;}
Object::const int& Value() const {return value;}
//The two codes make the program more versatile

//Create MIN Object
//Object fun(Object obj)
Object fun(const Object& obj) //const depends on whether the argument needs to be modified through formal parameters
{
	int val = obj.Value();
	Object obja(val); //3
	return obja; //4
}

int main()
{
	Object objx(0); //1
	Object objy(0); //2
	objy = fun(objx);
	return 0;
}

We design two Value functions to enhance the generality of the program; And the parameters passed to fun are modified to reference, so that the creation of an object is reduced


In the above code, we did not return the fun function by reference. This is the case:

  • Enter main function to allocate stack frame and create Objx and objy object space. There is no object in space creation; Build objects Objx and objy in this space at one time
  • Transfer the fun function to open up the fun function stack frame; Create an obj reference. The bottom layer actually points to objx, gives a val value of 10, and then constructs an obja object with a value of 10
  • return obja object: create a dead value object on the top of the main function stack frame (the main function calls fun), and send the address of the dead value object into eax to complete the construction of the dead value object; When the fun function ends, the stack frame of the function space is returned to the heap area, and the object obja is destructed
  • Return to the main function from the fun function and assign the dead value object pointed to by eax to obyy; Then destruct the dead value object
  • Transfer return 0 to the end of the main function; We deconstruct objy and then objx
  • Exit from main program after destruct

This time we use the reference feature to return

//Object fun(const Object& obj)
Object& fun(const Object& obj)
{
	int val = obj.Value() + 10;
	Object obja(val);
	return obja;
}

int main()
{
	Object objx(0);
	Object objy(0);
	objy = fun(objx);
	cout << objy.Value() << endl;
	return 0;
}


The main function stack frame creation process is the same as the function call process, but the function return is different

  • Enter main function to allocate stack frame and create Objx and objy object space. There is no object in space creation; Build objects Objx and objy in this space at one time
  • Transfer the fun function to open up the fun function stack frame; Create an obj reference. The bottom layer actually points to objx, gives a val value of 10, and then constructs an obja object with a value of 10
  • Return the obja reference. Instead of creating a dead value object, the obja address is given to eax. Then the frame space of the fun function stack is returned to the heap area, and the obja object is destructed
  • Back to the main function, we point to the object through eax dereference and tell the value to objy, but obja has been destructed and the space has been returned to the heap
  • If the space is not invaded, you can return 10. If it is invaded, you can return a random value; It's unreliable for us to get data from dead objects

Therefore, our reference return here is meaningless. It is wrong to reduce the creation of objects through reference return, which is equivalent to taking things from the dead

Why can't I return by reference

We design the following objects and write the default copy constructor and assignment function

class Object
{
private:
	int num;
	int ar[5];
public:
	Object(int n, int val = 0) :num(n)
	{
		for (int i = 0; i < n; ++i)
		{
			ar[i] = val;
		}
	}
};

int main()
{
	Object obja(5, 23);
	Object objb(obja);
	Object objc(5);
	objc = obja;

	return 0;
}

Next, we write the default copy constructor and assignment function:

  • The first is copy construction
Object(const Object& obj)
	{
	    //memmove(this, &obj, sizeof(Object));
	    //memset(this,0,sizeof(Object));
		num = obj.num;
		for (int i = 0; i < obj.num; i++)
		{
			ar[i] = obj.ar[i];
		}
	}

Memmove (this, & obj, sizeof (object)) is not allowed here; This is because: if some of our methods are virtual functions, when we create an object, the upper part of the object has 4 bytes pointing to the virtual table

Also use memset(this,0,sizeof(Object)); It is also wrong. When the type has a virtual table, the virtual table exists in the top four bytes of the object. The virtual table is set before entering the constructor or copying the constructor; Then, if we perform the above initialization, the virtual table will also be cleared

Therefore, in C + +, memory copy function should be used carefully in any member function!

  • Assignment function
Object& operator=(const Object& obj)
{
	if (&obj != this)
	{
		num = obj.num;
		for (int i = 0; i < obj.num; i++)
		{
			ar[i] = obj.ar[i];
		}
	}
	return *this;
}

What does the system do when we don't write copy construction or assignment functions?

For objc = obja, if there is no virtual function, the system will grasp the first address of obja and the first address of objc, and then copy the data values of obja into objc in turn; If there are virtual functions, the system will copy in the same way as we wrote above

When we don't write copy construction and assignment functions

class Object
{
private:
	int num;
	int ar[5];
public:
	Object(int n, int val = 0) :num(n)
	{
		for (int i = 0; i < n; ++i)
		{
			ar[i] = val;
		}
	}
	void Print() const
	{
		cout<< num << endl;
		for(int i = 0;i < 5;i++)
		{
			cout << ar[i] << endl;
		}
	}
};
Object& fun()
{
	Object objx(5,100);
	return objx;
}
int main()
{
	Object obja(5, 23);
	Object objb(obja);
	Object objc(5);
	objc = fun();

	objc.Print();

	return 0;
}

As we said before, when we reference and return an object; Instead of creating a dead value, we pass the objx address in the fun stack frame to eax and the value to objc. When we don't write the copy constructor and assignment function, the system will pass the value in turn according to the first address of obja and objc, so we can get the correct value

If we have an assignment statement, we will schedule the assignment statement, open up the stack frame and overwrite the objx remaining in the stack frame; Then printed out are random values

Let's summarize the reason: when there is no assignment statement in the code, the system will default an assignment statement. This default statement has no function call process, so there will be no on-site protection; When generating the default statement, the system detects whether the type is a simple type (there is no inheritance relationship, no virtual function, members only have basic types, and there is no type we designed), so the function of assignment statement will not be generated. It only copies the memory and will not invade the data remaining in the stack frame, so we can obtain this data; When we write the assignment statement in the code, the assignment statement will be scheduled, and then the stack frame of the original residual data will be disturbed

Everything we mentioned above is based on:

  • When the function returns a reference, it will pass the address of the object in the original function stack frame, and then the function ends the release of the space. Whether the correct value can be obtained depends on whether the space will be invaded
  • So never return an object as a reference anyway

Default construction and default assignment

class Object
{
	int num;
	int* ip;
public:
	Object(int n, int val = 0) : num(n)
	{
		ip = (int*)malloc(sizeof(int) * n);
		for (int i = 0; i < num; i++)
		{
			ip[i] = val;
		}
	}
	~Object()
	{
		free(ip);
		ip = NULL;
	}
};

int main()
{
	Object obja(5, 23);
	Object objb(obja);
	Object objc(8);
}

The following is the default copy structure and default assignment statement given by our system:

Object(const Object& obj)
{
	num = obj.num;
	ip = obj.ip;
}
Object& operator=(const Object& obj)
{
	if (this != &obj)
	{
		num = obj.num;
		ip = obj.ip;
	}
	return *this;
}


We copy objb here, which will direct the ip in objb to the ip space in obja instead of opening up a separate space; Similarly, when objc = obja, directly point the ip address in objc to the ip address in obja, then the memory space originally belonging to objc will be lost, and the same space will be released again when we analyze the object

Therefore, our default construction and default function cannot meet our requirements, so we need to rewrite them

Keywords: C++ Back-end Class stack

Added by moehome on Fri, 28 Jan 2022 23:33:23 +0200