sstruct *temp1;
sstruct *p;
sstruct *temp2=new sstruct;
If I try to do something like this
temp1=p;
temp2=p;
is there any diffrence?
The pointer p is not explicitly initialized. So if this is a local variable it has an indeterminate value. If, OTOH., it's in namespace scope, then it's zero-initialized.
Assuming that it's an indeterminate value the subsequent assignment temp1=p has Undefined Behavior. Anything might happen. These things that can happen include (1) nothing, and (2) a crash.
If nothing happens, then the second assignment, temp2=p, loses the original value of temp2, which was the pointer to a dynamically allocated object. So this then is a memory leak. On top of the Undefined Behavior.
In original Pascal pointers always pointed to dynamically allocated objects. There was no way to take the address of a variable. This was a somewhat higher level view of pointers, and the Pascal pointers were single object pointers, not pointers to arrays.
In original C, which C++ was based on, pointers were more like low level memory addresses. A C or C++ pointer value can be obtained by using the address operator, &, on some object. Or you can get a pointer as an implicit conversion of an expression referring to an array (you then get a pointer to the array's first item).
There are three main notations for denoting a nullvalue of pointer type.
int* p = 0; // Core language, original C.
int* p = nullptr; // Core language, C++11 and later.
int* p = NULL; // Library, `<stddef.h>`.
I recommend using the newer nullptr notation.
It's more readable and it fares better when used with so called perfect forwarding (whose main imperfection is that it doesn't deal correctly with 0, forwarding it as an int value).
First of all, since p is uninitialized you invoke undefined behavior by reading from it (if the code is in function scope). But for the following code
sstruct *temp1;
sstruct *p = nullptr;
sstruct *temp2=new sstruct;
temp1=p;
temp2=p;
both assignments are no different (sans the destination). temp1 and temp2 are left with nullptr value and the struct you previously allocated for temp2 has been leaked.
Related
Is the following well defined?
#include <iostream>
#include <string.h>
using namespace std;
struct Const {
const int i;
Const (int i) : i(i) {}
int get0() { return 0; } // best accessor ever!
};
int main() {
Const *q,*p = new Const(1);
new (p) Const(2);
memcpy (&q, &p, sizeof p);
cout << q->i;
return 0;
}
Note that after construction of second Const, p doesn't semantically (intentionally?) points to new object, and the first is gone, so it is usable "as a void*". But the second object is constructed at the exact same address, so the bit pattern of p represents the address of the new object.
COMMENT
new (p) Const(2) erase the old object stored at p, so the pointer is not valid anymore, except as a pointer to storage (void*).
I want to recover the value of p as a Const*.
COMMENT 2
After either p->~Const() or memset (p, 0, sizeof *p) it is clear that p does not point to a valid object, so p can only be used as pointer to storage (void* or char*), for example to reconstruct another object. At that point p->get0() is not allowed.
Here the demolition of the old object is done by the constructor of the new one, but I don't think that makes a difference.
My intuition is that: In any case, the old object is gone, and p points to the old object, not the new one.
I am looking for a confirmation or refutation based on the standard.
SEE ALSO
I have asked essentially the same question about pointers, in C and C++ :
Dereferencing an out of bound pointer that contains the address of an object (array of array)
Is memcpy of a pointer the same as assignment?
Are pointer variables just integers with some operators or are they "mystical"?
Please read these discussions before answering "this is ridiculous".
(making community-wiki as incorporating dyp's comment re 3.8/7 is very significant; while my earlier analysis was correct I would have said much the same things about code that was broken, having overlooked 3.8/7 myself)
Const *q,*p = new Const(1);
new (p) Const(2);
The new(p) Const(2); line overwrites the object that had been constructed with Const(1).
memcpy (&q, &p, sizeof p);
This is equivalent to q = p;.
cout << q->i;
This accesses the q->i member, which will be 2.
The somewhat noteworthy things are:
std::memcpy is an ugly way to assign p to q... it is legal though under 3.9/3:
For any trivially copyable type T, if two pointers to T point to distinct T objects obj1 and obj2, where neither obj1 nor obj2 is a base-class subobject, if the underlying bytes (1.7) making up obj1 are copied into obj2, obj2 shall subsequently hold the same value as obj1. [ Example:
T* t1p;
T* t2p;
// provided that t2p points to an initialized object ...
std::memcpy(t1p, t2p, sizeof(T));
// at this point, every subobject of trivially copyable type in *t1p contains
// the same value as the corresponding subobject in *t2p
The overwriting of the old Const(1) object with Const(2) is allowed as long as the program doesn't depend on side effects of the former's destructor, which it doesn't.
(as dyp noted in comments below) ongoing access to the Const(2) object using p is illegal under 3.8/7's third point:
pointer that pointed to the original object [...] can be used to manipulate the new object, if...
the type of the original object is not const-qualified, and, if a class type, does not contain any non-static data member whose type is const-qualified or a reference type ...
using q - rather than p - to access i is presumably necessary to avoid compiler optimisations based on presumed knowledge of i.
As for your commentary:
Note that after construction of second Const, p doesn't semantically (intentionally?) points to new object, and the first is gone, so it is usable "as a void*".
Given you placement-new an object at the address contained in p, p most certainly does point to the newly created object, and very intentionally, but it can't be used to manipulate that object under 3.8/7 as above.
Given you seem to have a notion of "semantically pointing" that's not defined in C++ the truth of that part of the statement's in your own mind.
'after construction of second Const, p...is usable "as a void*' makes no sense... it's not more usable as anything than it was beforehand.
But the second object is constructed at the exact same address, so the bit pattern of p represents the address of the new object.
Of course, but your comments show you think "bit pattern" is somehow distinct from the value of the pointer as applies to assignment with =, which is not true.
new (p) Const(2) erase the old object stored at p, so the pointer is not valid anymore, except as a pointer to storage (void*).
"erase" is a strange term for it... overwrites would be more meaningful. As dyp noted and explained above, 3.8/7 says you shouldn't "manipulate" the object p points to after the placement new, but the value and type of the pointer are unaffected by the placmeent new. Much as you can call f(void*) with a pointer to any type, the placement-new doesn't need to know or care about the type of the p expression.
After either p->~Const() or memset (p, 0, sizeof *p) it is clear that p does not point to a valid object, so p can only be used as pointer to storage (void* or char*), for example to reconstruct another object. At that point p->get0() is not allowed.
Most of that's true, if by "p can only be used" you mean the value of p at that time rather than the pointer itself (which can be of course also be assigned to). And you're trying to be a little too clever with the void* / char* thing - p remains a Const*, even if it's only used by placement new which doesn't care about the pointee type.
"I want to recover the value of p as a Const*."
The value of p was not changed after it was first initialised. placement-new uses the value - it does not modify it. There's nothing to recover as nothing was lost. That said, dyp's highlighted the need not to use p to manipulate the object, so while the value wasn't lost it's not directly usable as wanted either.
This is only intended as an addendum to #Tony D's answer, regarding
new (p) Const(2) erase the old object stored at p
I think you need to differentiate between an object and the conceptual idea of an "instance".
[...] An object is a region of storage.[...]
[N4431 §1.8/1]
So the pointer p points to a region of storage, which contains the bit pattern of some "instance" before the placement new and some different bit pattern of a different, but well constructed "instance" of the correct (same) type.
So at the location pointed to by p there's a valid object, and when assigning q from it q points to it. Though as noted in the other answer, accessing it via p isn't permited.
I wonder why I would need the second version?
int* p; // version 1
int* p = new int; // version 2
In the first version, the pointer isn't pointing at anything, it is undefined. Version 2 allocated memory and points p to that new memory. You are not allocating space for the pointer itself but memory for the pointer to point at. (In both versions the pointer itself is on the stack)
Assuming that the code appears in a function:
The first one defines a local variable of type int* (that is, a pointer). The variable is not initialized, which means the pointer doesn't have a value. It doesn't point at anything. It's nearly useless, about the only thing you can do with it is assign a pointer value to it[*]. So you think to yourself, "can I hold off defining the variable until I have a value to assign to it?"
The second one defines a local variable of type int* (that is a pointer), and also dynamically allocates an object of type int and assigns the address of that object to the pointer variable. So the pointer points to the int.
Dynamically allocating one int is nearly always a bad idea. It's not useless in the sense that you do at least have an int and a means to access it. But you've created a problem for yourself in that you have to keep track of it and free it.
[*] other things you can do with an uninitialized int* variable: take the address of the variable; bind it to a reference of type int*&; convert the address of the variable to char* and examine the memory one byte at a time, just to see what your implementation has put in that uninitialized variable. Nothing exciting and, crucially, nothing involving any int objects because you have none.
The first pointer
The first pointer, declared as:
int* p;
only allocates the memory needed to to store a pointer to int. The actual size is implementation defined. What the p object contains is indeterminate as per 8.5/12:
If no initializer is specified for an object, the object is default-initialized. When storage for an object with automatic or dynamic storage duration is obtained, the object has an indeterminate value, and if no initialization is performed for the object, that object retains an indeterminate value until that value is replaced (5.17).
This means that dereferencing the pointer will lead to undefined behavior.
The second pointer
The second pointer, declared as:
int* p = new int;
dynamically allocates an int. This means that the lifetime of the object will terminate either at the exit of the program (not sure if the standard actually enforces this, but I'm pretty sure the underlying OS will take back the unused memory once the program is done executing) or when you free it.
This pointer can be dereferenced safely, unless operator new failed to allocate memory (in which case it will throw std::bad_alloc or, since C++11, another exception derived from std::bad_alloc).
Why the second pointer shouldn't be used in most cases
Memory management is an hard topic. The main tip that I can give you, is to avoid new and delete like a plague. Whenever you can do something in any other standard way, you should prefer it.
For example, in this case, the only reason I can come up with to justify such a technique is to have an optional parameter. You could, and should, std::optional instead.
I read Are negative array indexes allowed in C? and found it interesting that negative values can be used for the index of an array. I tried it again with the c++11 unique_ptr and it works there as well! Of course the deleter must be replaced with something which can delete the original array. Here is what it looks like:
#include <iostream>
#include <memory>
int main()
{
const int min = -23; // the smaller valid index
const int max = -21; // the highest valid index
const auto deleter = [min](char* p)
{
delete [](p+min);
};
std::unique_ptr<char[],decltype(deleter)> up(new char[max-min+1] - min, deleter);
// this works as expected
up[-23] = 'h'; up[-22] = 'i'; up[-21] = 0;
std::cout << (up.get()-23) << '\n'; // outputs:hi
}
I'm wondering if there is a very, very small chance that there is a memory leak. The address of the memory created on the heap (new char[max-min+1]) could overflow when adding 23 to it and become a null pointer. Subtracting 23 still yields the array's original address, but the unique_ptr may recognize it as a null pointer. The unique_ptr may not delete it because it's null.
So, is there a chance that the previous code will leak memory or does the smart pointer behave in a way which makes it safe?
Note: I wouldn't actually use this in actual code; I'm just interested in how it would behave.
Edit: icepack brings up an interesting point, namely that there are only two valid pointer values that are allowed in pointer arithmetic:
§5.7 [expr.add] p5
If both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined.
As such, the new char[N] - min of your code already invokes UB.
Now, on most implementations, this will not cause problems. The destructor of std::unique_ptr, however, will (pre-edit answer from here on out):
§20.7.1.2.2 [unique.ptr.single.dtor] p2
Effects: If get() == nullptr there are no effects. Otherwise get_deleter()(get()).
So yes, there is a chance that you will leak memory here if it indeed maps to whatever value represents the null pointer value (most likely 0, but not necessarily). And yes, I know this is the one for single objects, but the array one behaves exactly the same:
§20.7.1.3 [unique.ptr.runtime] p2
Descriptions are provided below only for member functions that have behavior different from the primary template.
And there is no description for the destructor.
new char[max-min+1] doesn't allocate memory on the stack but rather on heap - that's how standard operator new behaves. The expression max-min+1 is evaluated by the compiler and results in 3, so eventually this expression is equal to allocating 3 bytes on the heap. No problem here.
However, subtracting min results in pointer which is 23 bytes beyond the beginning of the allocated memory returned by new and since in new you allocated only 3 bytes, this will definitely point to a location not owned by you --> anything following will result in undefined behavior.
I searched in the web but couldn't find a reliable answer.
And what would
someclass* ptr = 1;
char* charptr = 2;
or assigning them to any other integer mean?
What happens if I don't initialize pointers (for native data pointers as well as class pointers) before using them?
Setting a pointer to 0 is equivalent to setting it to NULL. However, this is only true for a constant expression 0, i.e. for compile-time zero value. Trying to set a pointer to a run-time zero value is not guaranteed to produce a null pointer
int *pi = 0; // Initializes a null pointer
char *pc = 2 - 2; // Initializes a null pointer
short *ps = sizeof *pc - 1; // Initializes a null pointer
int x = 0;
double *pd = (double *) x;
// Implementation-defined, not guaranteed to produce a null pointer
You can explore the matter in greater detail C FAQ
To answer the second part of your question:
It is illegal to assign any other integer (besides literal/constant 0) to a pointer. Neither of your initializations (or assignments) will compile. It is illegal in both C and C++, although C compilers are historically more permitting in this regard, responding with a warning instead of refusing to compile the code.
Also, in C++ language there's no difference of how pointers to class types are treated. Pointer to class types are still considered scalar types and behave in this regard the same way as any pointer to a fundamental type does.
Yes, in C++ NULL is defined to be 0. Setting a pointer to some other small integer value would mean it pointed to a -- likely illegal -- portion of the computer's memory (but it wouldn't be considered a NULL pointer).
If you don't initialize a global pointer it will be set to zero (NULL) for you just before the program starts. If you don't initialize pointer variables declared on the stack (i.e. as within functions or methods) they will have garbage in them. Likewise, the contents of any dynamically allocated pointer or any contained in an object as a data member also will have no predefined value. Some compilers have extensions that allow requests for dynamically allocated memory to be initially zeroed, like C's calloc() function does for raw memory.
Please include an example with the explanation.
Reviewing the basic terminology
It's usually good enough - unless you're programming assembly - to envisage a pointer containing a numeric memory address, with 1 referring to the second byte in the process's memory, 2 the third, 3 the fourth and so on....
What happened to 0 and the first byte? Well, we'll get to that later - see null pointers below.
For a more accurate definition of what pointers store, and how memory and addresses relate, see "More about memory addresses, and why you probably don't need to know" at the end of this answer.
When you want to access the data/value in the memory that the pointer points to - the contents of the address with that numerical index - then you dereference the pointer.
Different computer languages have different notations to tell the compiler or interpreter that you're now interested in the pointed-to object's (current) value - I focus below on C and C++.
A pointer scenario
Consider in C, given a pointer such as p below...
const char* p = "abc";
...four bytes with the numerical values used to encode the letters 'a', 'b', 'c', and a 0 byte to denote the end of the textual data, are stored somewhere in memory and the numerical address of that data is stored in p. This way C encodes text in memory is known as ASCIIZ.
For example, if the string literal happened to be at address 0x1000 and p a 32-bit pointer at 0x2000, the memory content would be:
Memory Address (hex) Variable name Contents
1000 'a' == 97 (ASCII)
1001 'b' == 98
1002 'c' == 99
1003 0
...
2000-2003 p 1000 hex
Note that there is no variable name/identifier for address 0x1000, but we can indirectly refer to the string literal using a pointer storing its address: p.
Dereferencing the pointer
To refer to the characters p points to, we dereference p using one of these notations (again, for C):
assert(*p == 'a'); // The first character at address p will be 'a'
assert(p[1] == 'b'); // p[1] actually dereferences a pointer created by adding
// p and 1 times the size of the things to which p points:
// In this case they're char which are 1 byte in C...
assert(*(p + 1) == 'b'); // Another notation for p[1]
You can also move pointers through the pointed-to data, dereferencing them as you go:
++p; // Increment p so it's now 0x1001
assert(*p == 'b'); // p == 0x1001 which is where the 'b' is...
If you have some data that can be written to, then you can do things like this:
int x = 2;
int* p_x = &x; // Put the address of the x variable into the pointer p_x
*p_x = 4; // Change the memory at the address in p_x to be 4
assert(x == 4); // Check x is now 4
Above, you must have known at compile time that you would need a variable called x, and the code asks the compiler to arrange where it should be stored, ensuring the address will be available via &x.
Dereferencing and accessing a structure data member
In C, if you have a variable that is a pointer to a structure with data members, you can access those members using the -> dereferencing operator:
typedef struct X { int i_; double d_; } X;
X x;
X* p = &x;
p->d_ = 3.14159; // Dereference and access data member x.d_
(*p).d_ *= -1; // Another equivalent notation for accessing x.d_
Multi-byte data types
To use a pointer, a computer program also needs some insight into the type of data that is being pointed at - if that data type needs more than one byte to represent, then the pointer normally points to the lowest-numbered byte in the data.
So, looking at a slightly more complex example:
double sizes[] = { 10.3, 13.4, 11.2, 19.4 };
double* p = sizes;
assert(p[0] == 10.3); // Knows to look at all the bytes in the first double value
assert(p[1] == 13.4); // Actually looks at bytes from address p + 1 * sizeof(double)
// (sizeof(double) is almost always eight bytes)
++p; // Advance p by sizeof(double)
assert(*p == 13.4); // The double at memory beginning at address p has value 13.4
*(p + 2) = 29.8; // Change sizes[3] from 19.4 to 29.8
// Note earlier ++p and + 2 here => sizes[3]
Pointers to dynamically allocated memory
Sometimes you don't know how much memory you'll need until your program is running and sees what data is thrown at it... then you can dynamically allocate memory using malloc. It is common practice to store the address in a pointer...
int* p = (int*)malloc(sizeof(int)); // Get some memory somewhere...
*p = 10; // Dereference the pointer to the memory, then write a value in
fn(*p); // Call a function, passing it the value at address p
(*p) += 3; // Change the value, adding 3 to it
free(p); // Release the memory back to the heap allocation library
In C++, memory allocation is normally done with the new operator, and deallocation with delete:
int* p = new int(10); // Memory for one int with initial value 10
delete p;
p = new int[10]; // Memory for ten ints with unspecified initial value
delete[] p;
p = new int[10](); // Memory for ten ints that are value initialised (to 0)
delete[] p;
See also C++ smart pointers below.
Losing and leaking addresses
Often a pointer may be the only indication of where some data or buffer exists in memory. If ongoing use of that data/buffer is needed, or the ability to call free() or delete to avoid leaking the memory, then the programmer must operate on a copy of the pointer...
const char* p = asprintf("name: %s", name); // Common but non-Standard printf-on-heap
// Replace non-printable characters with underscores....
for (const char* q = p; *q; ++q)
if (!isprint(*q))
*q = '_';
printf("%s\n", p); // Only q was modified
free(p);
...or carefully orchestrate reversal of any changes...
const size_t n = ...;
p += n;
...
p -= n; // Restore earlier value...
free(p);
C++ smart pointers
In C++, it's best practice to use smart pointer objects to store and manage the pointers, automatically deallocating them when the smart pointers' destructors run. Since C++11 the Standard Library provides two, unique_ptr for when there's a single owner for an allocated object...
{
std::unique_ptr<T> p{new T(42, "meaning")};
call_a_function(p);
// The function above might throw, so delete here is unreliable, but...
} // p's destructor's guaranteed to run "here", calling delete
...and shared_ptr for share ownership (using reference counting)...
{
auto p = std::make_shared<T>(3.14, "pi");
number_storage1.may_add(p); // Might copy p into its container
number_storage2.may_add(p); // Might copy p into its container } // p's destructor will only delete the T if neither may_add copied it
Null pointers
In C, NULL and 0 - and additionally in C++ nullptr - can be used to indicate that a pointer doesn't currently hold the memory address of a variable, and shouldn't be dereferenced or used in pointer arithmetic. For example:
const char* p_filename = NULL; // Or "= 0", or "= nullptr" in C++
int c;
while ((c = getopt(argc, argv, "f:")) != -1)
switch (c) {
case f: p_filename = optarg; break;
}
if (p_filename) // Only NULL converts to false
... // Only get here if -f flag specified
In C and C++, just as inbuilt numeric types don't necessarily default to 0, nor bools to false, pointers are not always set to NULL. All these are set to 0/false/NULL when they're static variables or (C++ only) direct or indirect member variables of static objects or their bases, or undergo zero initialisation (e.g. new T(); and new T(x, y, z); perform zero-initialisation on T's members including pointers, whereas new T; does not).
Further, when you assign 0, NULL and nullptr to a pointer the bits in the pointer are not necessarily all reset: the pointer may not contain "0" at the hardware level, or refer to address 0 in your virtual address space. The compiler is allowed to store something else there if it has reason to, but whatever it does - if you come along and compare the pointer to 0, NULL, nullptr or another pointer that was assigned any of those, the comparison must work as expected. So, below the source code at the compiler level, "NULL" is potentially a bit "magical" in the C and C++ languages...
More about memory addresses, and why you probably don't need to know
More strictly, initialised pointers store a bit-pattern identifying either NULL or a (often virtual) memory address.
The simple case is where this is a numeric offset into the process's entire virtual address space; in more complex cases the pointer may be relative to some specific memory area, which the CPU may select based on CPU "segment" registers or some manner of segment id encoded in the bit-pattern, and/or looking in different places depending on the machine code instructions using the address.
For example, an int* properly initialised to point to an int variable might - after casting to a float* - access memory in "GPU" memory quite distinct from the memory where the int variable is, then once cast to and used as a function pointer it might point into further distinct memory holding machine opcodes for the program (with the numeric value of the int* effectively a random, invalid pointer within these other memory regions).
3GL programming languages like C and C++ tend to hide this complexity, such that:
If the compiler gives you a pointer to a variable or function, you can dereference it freely (as long as the variable's not destructed/deallocated meanwhile) and it's the compiler's problem whether e.g. a particular CPU segment register needs to be restored beforehand, or a distinct machine code instruction used
If you get a pointer to an element in an array, you can use pointer arithmetic to move anywhere else in the array, or even to form an address one-past-the-end of the array that's legal to compare with other pointers to elements in the array (or that have similarly been moved by pointer arithmetic to the same one-past-the-end value); again in C and C++, it's up to the compiler to ensure this "just works"
Specific OS functions, e.g. shared memory mapping, may give you pointers, and they'll "just work" within the range of addresses that makes sense for them
Attempts to move legal pointers beyond these boundaries, or to cast arbitrary numbers to pointers, or use pointers cast to unrelated types, typically have undefined behaviour, so should be avoided in higher level libraries and applications, but code for OSes, device drivers, etc. may need to rely on behaviour left undefined by the C or C++ Standard, that is nevertheless well defined by their specific implementation or hardware.
Dereferencing a pointer means getting the value that is stored in the memory location pointed by the pointer. The operator * is used to do this, and is called the dereferencing operator.
int a = 10;
int* ptr = &a;
printf("%d", *ptr); // With *ptr I'm dereferencing the pointer.
// Which means, I am asking the value pointed at by the pointer.
// ptr is pointing to the location in memory of the variable a.
// In a's location, we have 10. So, dereferencing gives this value.
// Since we have indirect control over a's location, we can modify its content using the pointer. This is an indirect way to access a.
*ptr = 20; // Now a's content is no longer 10, and has been modified to 20.
In simple words, dereferencing means accessing the value from a certain memory location against which that pointer is pointing.
A pointer is a "reference" to a value.. much like a library call number is a reference to a book. "Dereferencing" the call number is physically going through and retrieving that book.
int a=4 ;
int *pA = &a ;
printf( "The REFERENCE/call number for the variable `a` is %p\n", pA ) ;
// The * causes pA to DEREFERENCE... `a` via "callnumber" `pA`.
printf( "%d\n", *pA ) ; // prints 4..
If the book isn't there, the librarian starts shouting, shuts the library down, and a couple of people are set to investigate the cause of a person going to find a book that isn't there.
Code and explanation from Pointer Basics:
The dereference operation starts at
the pointer and follows its arrow over
to access its pointee. The goal may be
to look at the pointee state or to
change the pointee state. The
dereference operation on a pointer
only works if the pointer has a
pointee -- the pointee must be
allocated and the pointer must be set
to point to it. The most common error
in pointer code is forgetting to set
up the pointee. The most common
runtime crash because of that error in
the code is a failed dereference
operation. In Java the incorrect
dereference will be flagged politely
by the runtime system. In compiled
languages such as C, C++, and Pascal,
the incorrect dereference will
sometimes crash, and other times
corrupt memory in some subtle, random
way. Pointer bugs in compiled
languages can be difficult to track
down for this reason.
void main() {
int* x; // Allocate the pointer x
x = malloc(sizeof(int)); // Allocate an int pointee,
// and set x to point to it
*x = 42; // Dereference x to store 42 in its pointee
}
I think all the previous answers are wrong, as they
state that dereferencing means accessing the actual value.
Wikipedia gives the correct definition instead:
https://en.wikipedia.org/wiki/Dereference_operator
It operates on a pointer variable, and returns an l-value equivalent to the value at the pointer address. This is called "dereferencing" the pointer.
That said, we can dereference the pointer without ever
accessing the value it points to. For example:
char *p = NULL;
*p;
We dereferenced the NULL pointer without accessing its
value. Or we could do:
p1 = &(*p);
sz = sizeof(*p);
Again, dereferencing, but never accessing the value. Such code will NOT crash:
The crash happens when you actually access the data by an
invalid pointer. However, unfortunately, according the the
standard, dereferencing an invalid pointer is an undefined
behaviour (with a few exceptions), even if you don't try to
touch the actual data.
So in short: dereferencing the pointer means applying the
dereference operator to it. That operator just returns an
l-value for your future use.