I'm trying to learn how C++ compilers handle references and pointers, in preparation for a compiler class that I'm taking next semester. I'm specifically interested in how compilers handle references in C++.
The standard specifies that a reference is an "alias," but I don't know exactly what that means at the compiler level. I have two theories:
A non-reference variable has an entry in the symbol table. When a reference to that variable is created, the compiler simply creates another lexeme that "points" to the exact same entry in the symbol table (and not to the non-reference variable's location in memory).
When a reference to that variable is created, the compiler creates a pointer to that variable's location in memory. The limitations on references (no null values, etc.) are handled when parsing the context of the language. In other words, a reference is "syntactic sugar" for a dereferenced pointer.
Both solutions would create an "alias," as far as I can tell. Do compilers use one and not the other? Or is it compiler-dependent?
As an aside, I'm aware that at the machine-language level, both are "pointers" (pretty much everything other than an integer is a "pointer" at the machine level). I'm interested in what the compiler does before the machine code is generated.
EDIT: Part of the reason I am curious is because PHP uses method #1, and I'm wondering if C++ compilers work the same way. Java certainly does not use method #1, and their "references" are in fact dereferenced pointers; see this article by Scott Stanchfield.
I will try to explain how references are implemented by g++ compiler.
#include <iostream>
using namespace std;
int main()
{
int i = 10;
int *ptrToI = &i;
int &refToI = i;
cout << "i = " << i << "\n";
cout << "&i = " << &i << "\n";
cout << "ptrToI = " << ptrToI << "\n";
cout << "*ptrToI = " << *ptrToI << "\n";
cout << "&ptrToI = " << &ptrToI << "\n";
cout << "refToNum = " << refToI << "\n";
//cout << "*refToNum = " << *refToI << "\n";
cout << "&refToNum = " << &refToI << "\n";
return 0;
}
Output of this code is like this
i = 10
&i = 0xbf9e52f8
ptrToI = 0xbf9e52f8
*ptrToI = 10
&ptrToI = 0xbf9e52f4
refToNum = 10
&refToNum = 0xbf9e52f8
Lets look at the disassembly(I used GDB for this. 8,9 and 10 here are line numbers of code)
8 int i = 10;
0x08048698 <main()+18>: movl $0xa,-0x10(%ebp)
Here $0xa is the 10(decimal) that we are assigning to i. -0x10(%ebp) here means content of ebp register –16(decimal).
-0x10(%ebp) points to the address of i on stack.
9 int *ptrToI = &i;
0x0804869f <main()+25>: lea -0x10(%ebp),%eax
0x080486a2 <main()+28>: mov %eax,-0x14(%ebp)
Assign address of i to ptrToI. ptrToI is again on stack located at address -0x14(%ebp), that is ebp – 20(decimal).
10 int &refToI = i;
0x080486a5 <main()+31>: lea -0x10(%ebp),%eax
0x080486a8 <main()+34>: mov %eax,-0xc(%ebp)
Now here is the catch! Compare disassembly of line 9 and 10 and you will observer that ,-0x14(%ebp) is replaced by -0xc(%ebp) in line number 10. -0xc(%ebp) is the address of refToNum. It is allocated on stack. But you will never be able to get this address from you code because you are not required to know the address.
So; a reference does occupy memory. In this case it is the stack memory since we have allocated it as a local variable.
How much memory does it occupy?
As much a pointer occupies.
Now lets see how we access the reference and pointers. For simplicity I have shown only part of the assembly snippet
16 cout << "*ptrToI = " << *ptrToI << "\n";
0x08048746 <main()+192>: mov -0x14(%ebp),%eax
0x08048749 <main()+195>: mov (%eax),%ebx
19 cout << "refToNum = " << refToI << "\n";
0x080487b0 <main()+298>: mov -0xc(%ebp),%eax
0x080487b3 <main()+301>: mov (%eax),%ebx
Now compare the above two lines, you will see striking similarity. -0xc(%ebp) is the actual address of refToI which is never accessible to you.
In simple terms, if you think of reference as a normal pointer, then accessing a reference is like fetching the value at address pointed to by the reference. Which means the below two lines of code will give you the same result
cout << "Value if i = " << *ptrToI << "\n";
cout << " Value if i = " << refToI << "\n";
Now compare this
15 cout << "ptrToI = " << ptrToI << "\n";
0x08048713 <main()+141>: mov -0x14(%ebp),%ebx
21 cout << "&refToNum = " << &refToI << "\n";
0x080487fb <main()+373>: mov -0xc(%ebp),%eax
I guess you are able to spot what is happening here.
If you ask for &refToI, the contents of -0xc(%ebp) address location are returned and -0xc(%ebp) is where refToi resides and its contents are nothing but address of i.
One last thing, Why is this line commented?
//cout << "*refToNum = " << *refToI << "\n";
Because *refToI is not permitted and it will give you a compile time error.
Understanding pointers and references is quite different than implementing the code for them.
I suggest you learn how to use them properly and focus on the core of compiler theory. The fundamental compiler theory class is difficult enough without the concepts of pointers, references and inheritance. Pointers and references are left for a more advanced class.
Simply put: use references when you can, pointers when you must.
Edit 1:
Compilers can implement references and pointers in any way they want as long as their syntax and semantics behave according to the language specification.
A simple implementation is to treat references as pointers with additional attributes.
Everything in memory has a location, i.e. address. The compiler may have to use internal pointers to load from memory into registers and to store register contents into memory. So to refer to a variable in memory, whether by pointer, reference or alias, the compiler needs the address of the variable. (This does not include register variables which are treated differently.) So using pointers for references or aliases saves some coding.
Related
I thought the following code snippets would cause double free, and the program would core dump. But the truth is that there is no error when I run the code?
Similar problem shows that it caused double free!
My Question is why does there have no error show that there is a double free? And why does there have no core dump?
#include <iostream>
using namespace std;
int main()
{
int *p = new int(5);
cout << "The value that p points to: " << (*p) << endl;
cout << "The address that p points to: " << &(*p) << endl;
delete p;
cout << "The value that p points to: " << (*p) << endl;
cout << "The address that p points to: " << &(*p) << endl;
delete p;
cout << "The value that p points to: " << (*p) << endl;
cout << "The address that p points to: " << &(*p) << endl;
delete p;
}
The program's output when I ran this program is shown as followed:
After modifying the code snippet like the following, the core dump occured:
#include <iostream>
using namespace std;
int main()
{
int *p = new int(5);
for (;;)
{
cout << "The value that p points to: " << (*p) << endl;
cout << "The address that p points to: " << &(*p) << endl;
delete p;
}
return 0;
}
And the program output is :
So there is another question that why this program will core dump every time?
Yes, it is a double free (well, triple, really) which puts it into undefined behaviour territory.
But that's the insidious thing about undefined behaviour, it's not required to crash or complain, it's not required to do anything at all(a). It may even work.
I can envisage an implementation that stores the free state of a block in the control information for it so that freeing it twice would have no effect. However, that would be inefficient, and also wouldn't cover the case where it had been reallocated for another purpose (it would prevent double frees, but not a piece of code freeing the block when some other piece still thinks it still has it).
So, given it's not required to work, you would be well advised to steer clear of it since it may also download maniacal_laughter.ogg and play it while erasing your primary drive.
As an aside, modern C++ has smart pointers that are able to manage their own lifetime, and you would be doing yourself a big favour if you started using those instead of raw pointers.And, although the removal of raw pointer from C++ was a joke, there are some that think it's not such a bad idea :-)
(a) The C++20 standard has this to say when describing undefined behaviour in [defns.undefined] (my emphasis):
Behavior for which this document imposes **NO** requirements.
why does there have no error show that there is a double free? And why does there have no core dump?
delete p;
cout << "The value that p points to: " << (*p) << endl;
The moment you referenced to a deleted pointer is when the program entered an undefined behaviour, and then there is no guarantee that there would be an error or a crash.
It's not entirely the same, but the analogy between memory and a hotel room is applicable, which explains well what an undefined behaviour means. Highly recommended reading:
Can a local variable's memory be accessed outside its scope?
I'm new to c++ and I have recently spent a couple of days reading about pointers.I realized that the 2 following codes give me different results although they seem identical.
the first code:
int a = 5;
int* ptr = &a;
cout << ptr;
cout << "\n" << ++ptr;
the second code:
int a = 5;
int* ptr = &a;
cout << ptr << "\n" << ++ptr;
here is the output of the first one:
0043F940
0043F944
the output of the second one:
003AFE20
003AFE20
the first one seems more logical to me since it first outputs the address of a and then the address of the next integer location.But in the second one ptr is apparently always pointing to a.
Can someone explain this difference to me?
Thank you in advance.
Look closely at: cout << ptr << "\n" << ++ptr;
Your code is like f (ptr, ++ptr); which has the same issue.
Both lines of code have two places where the value of ptr is read and one place where the value of ptr is written. While the read in ++ptr has to take place before the write, the read of ptr (for << ptr or the first parameter to f) has no restrictions on when it takes place and can take place either before or after the write.
This is because C++ does not specify the order in which a function's parameters are evaluated. Apparently, on your platform, with your compiler flags, the increment happens to take place before the other read. So you output the incremented value twice.
Let us name the operator<<(a, b) now as function f(a, b).
Using this notation cout << 5 becomes f(cout, 5). Using the fact that the << operator has left-to-right associativity, cout << 4 << "a" becomes f(f(cout, 4), "a").
Let's translate your code:
cout << ptr << "\n" << ++ptr;
to:
f(f(f(cout, ptr), "\n"), ++ptr);
As you can see, the most outer call to f has as second argument ++ptr. Since C++ doesn't specify the order of evaluation of arguments to a function call, it is free to increment ptr first, before evaluating the nested f(...) function call.
Im trying to understand the delete operator in c++.
I could understand the logic behind using pointers and new operator, yet I understood that "The delete operator eliminates a dynamic variable and returns the memory that the dynamic variable occupied to the freestone." p517, Problem solving with C++ 9th edition.
which, I think doesn't align with the third cout statement. and I expected that the third cout statement to be sth similar to the first one.
int main() {
int *p1;
cout << "p1 address: " << &p1 << " and points to the address "<< p1 << " which has the value: " << *p1<< endl;
p1 = new int;
cout << "p1 address: " << &p1 << " and points to the address "<< p1 << " which has the value: " << *p1<< endl;
delete p1;
cout << "p1 address: " << &p1 << " and points to the address "<< p1 << " which has the value: " << *p1<< endl;
cout << endl;
return 0;
}
I would appreciate any explanation :))
delete doesn't have to change the memory your pointer points to. What it actually does is implementation specific.
What delete needs to do is to deconstruct any object at the given address and return the associated memory to the allocation pool. Some debugger might overwrite the value of a variable when freed, but for trivial types no special deconstruction needs to take place - the memory can be returned to the pool as-is. The pointer isn't changed either: after delete p we call p a dangling pointer that holds an address to freed memory. All access through that pointer is undefined behaviour.
Since handling raw pointers and especially dangling pointers is error prone, it's good to know the C++ smartpointers, e.g. unique_ptr:
std::unique_ptr<int> p; // initialised to nullptr
p = std::make_unique<int>(13); // new int with value 13
p = std::make_unique<int>(37); // deleted previous int, assigned new int with value 37
// optional (when p goes out of scope its pointee is deleted automatically)
p.reset(); // deleted the int and reset p to nullptr
I am new to cpp and learning pointers. while practicing the codes i came across this. When i do &(a pointer), it gives an another memory location.i want to know what this address is. I assume its the location where the ptr is saved. it has to be saved somewhere though it has the memory of value variable.
#include<iostream>
using namespace std;
int main()
{
int v =6;
int *ptr;
ptr = &v;
cout << "1: " << ptr << endl;
cout << "2: " << *ptr << endl;
cout << "3: " << &ptr << endl;
return 0;
}
OUTPUT:
1: 0x29cc6c
2: 6
3: 0x29cc68
Correct.
Output 1 is the value of the pointer, which is the address that it points to.
Output 2 is the "dereferenced" pointer which means it is the value contained at Output 1 (the location the pointer is pointing to).
Output 3 is the location where the pointer (which contains the value from output 1) is being stored.
You got it perfectly right:
v is a variable, so &v is the adress where v is stored in memory;
ptr is a variable as well, so &ptr is the adress where ptr is stored in memory.
In your example, because these variables are stored one after the other (on the local stack), their adresses are only 4 bytes apart.
I started noticing that sometimes when deallocating memory in some of my programs, they would inexplicably crash. I began narrowing down the culprit and have come up with an example that illustrates a case that I am having difficulty understanding:
#include <iostream>
#include <stdlib.h>
using namespace std;
int main() {
char *tmp = (char*)malloc(16);
char *tmp2 = (char*)malloc(16);
long address = reinterpret_cast<long>(tmp);
long address2 = reinterpret_cast<long>(tmp2);
cout << "tmp = " << address << "\n";
cout << "tmp2 = " << address2 << "\n";
memset(tmp, 1, 16);
memset(tmp2, 1, 16);
char startBytes[4] = {0};
char endBytes[4] = {0};
memcpy(startBytes, tmp - 4, 4);
memcpy(endBytes, tmp + 16, 4);
cout << "Start: " << static_cast<int>(startBytes[0]) << " " << static_cast<int>(startBytes[1]) << " " << static_cast<int>(startBytes[2]) << " " << static_cast<int>(startBytes[3]) << "\n";
cout << "End: " << static_cast<int>(endBytes[0]) << " " << static_cast<int>(endBytes[1]) << " " << static_cast<int>(endBytes[2]) << " " << static_cast<int>(endBytes[3]) << "\n";
cout << "---------------\n";
free(tmp);
memcpy(startBytes, tmp - 4, 4);
memcpy(endBytes, tmp + 16, 4);
cout << "Start: " << static_cast<int>(startBytes[0]) << " " << static_cast<int>(startBytes[1]) << " " << static_cast<int>(startBytes[2]) << " " << static_cast<int>(startBytes[3]) << "\n";
cout << "End: " << static_cast<int>(endBytes[0]) << " " << static_cast<int>(endBytes[1]) << " " << static_cast<int>(endBytes[2]) << " " << static_cast<int>(endBytes[3]) << "\n";
free(tmp2);
return 0;
}
Here is the output that I am seeing:
tmp = 8795380
tmp2 = 8795400
Start: 16 0 0 0
End: 16 0 0 0
---------------
Start: 17 0 0 0
End: 18 0 0 0
I am using Borland's free compiler. I am aware that the header bytes that I am looking at are implementation specific, and that things like "reinterpret_cast" are bad practice. The question I am merely looking to find an answer to is: why does the first byte of "End" change from 16 to 18?
The 4 bytes that are considered "end" are 16 bytes after tmp, which are 4 bytes before tmp2. They are tmp2's header - why does a call to free() on tmp affect this place in memory?
I have tried the same example using new [] and delete [] to create/delete tmp and tmp2 and the same results occur.
Any information or help in understanding why this particular place in memory is being affected would be much appreciated.
You will have to ask your libc implementation why it changes. In any case, why does it matter? This is a memory area that libc has not allocated to you, and may be using to maintain its own data structures or consistency checks, or may not be using at all.
Basically you are looking at memory you didn't allocate. You can't make any supposition on what happens to the memory outside what you requested (ie the 16 bytes you allocated). There is nothing abnormal going on.
The runtime and compilers are free to do whatever they want to do with them so you should not use them in your programs. The runtime probably change the values of those bytes to keep track of its internal state.
Deallocating memory is very unlikely to crash a program. On the other hand, accessing memory you have deallocated like in your sample is big programming mistake that is likely to do so.
A good way to avoid this is to set any pointers you free to NULL. Doing so you'll force your program to crash when accessing freed variables.
It's possible that the act of removing an allocated element from the heap modifies other heap nodes, or that the implementation reserves one or more bytes of headers for use as guard bytes from previous allocations.
The memory manager must remember for example what is the size of the memory block that has been allocated with malloc. There are different ways, but probably the simplest one is to just allocate 4 bytes more than the size requested in the call and store the size value just before the pointer returned to the caller.
The implementation of free can then subtract 4 bytes from the passed pointer to get a pointer to where the size has been stored and then can link the block (for example) to a list of free reusable blocks of that size (may be using again those 4 bytes to store the link to next block).
You are not supposed to change or even look at bytes before/after the area you have allocated. The result of accessing, even just for reading, memory that you didn't allocate is Undefined Behavior (and yes, you really can get a program to really crash or behave crazily just because of reading memory that wasn't allocated).