Operations with pointers. What does it mean in C: what is a pointer? What is stored in a variable of type pointer

A pointer is a derived type that represents the address of some value. The C++ language uses the concept of variable addresses. Working with addresses was inherited by C++ from the C language. Let's assume that a variable of type int is defined in the program:

int x;

You can define a pointer variable to an integer:

int* xptr;

and assign the xptr variable the address of the x variable:

xptr =

Need to remember:

  • The & operation applied to a variable is an address operation.
  • The * operation applied to an address (in other words, applied to a pointer) is an address operation.

Thus, for the previous definitions of x and y, the two operators are equivalent:

// assign variable y the value x

int y = x;

// assign the variable y the value

// located at xptr

int y = *xptr;

Using the address operation, you can write values:

// write the number 10 to address xptr

*xptr = 10;

After executing this statement, the value of variable x will be 10 because xptr points to variable x.

A pointer is not just an address, but an address of a value of a certain type. The xptr pointer is the address of an integer value. You can define the addresses of quantities of other types as follows:

// pointer to unsigned integer

unsigned long* lPtr;

// pointer to byte

char* cp;

// pointer to an object of the Complex class

Complex* p;

If the pointer refers to an object of some class, then the operation of accessing the class attribute instead of a dot is denoted by “->”, for example p->real. If you recall one of the previous examples:

void Complex::Add(Complex x)

This->real = this->real + x.real;

This->imaginary = this->imaginary +

X.imaginary;

then this is a pointer to the current object, i.e. an object that executes the Add method. The this-> entry means accessing an attribute of the current object.

You can define a pointer to any type, including a function or class method. If there are several functions of the same type:

int foo(long x);

int bar(long x);

You can define a variable of type pointer to a function and call these functions not directly, but indirectly, through the pointer:

int (*functptr)(long x);

functionptr =

(*funcptr)(2);

functionptr =

(*funcptr)(4);

What are pointers for? Pointers appeared primarily for the needs of system programming. Since the C language was intended for “low-level” programming, it was necessary to access, for example, device registers. These device registers have very specific addresses, i.e. it was necessary to read or write a value at a specific address. Thanks to the pointer mechanism, such operations do not require any additional language tools.

int* hardwareRegiste =0x80000;

*hardwareRegiste =12;

However, the use of pointers is not limited to the needs of system programming. Pointers can significantly simplify and speed up a number of operations. Suppose the program has a memory area for storing intermediate results of calculations. This memory area is used by different program modules. Instead of copying this memory area every time we access the module, we can pass a pointer as an argument to the function call, thereby simplifying and speeding up calculations.

Last update: 05/27/2017

Pointers in the C language support a number of operations: assignment, getting the address of a pointer, getting a value from a pointer, some arithmetic operations and comparison operations.

Assignment

A pointer can be assigned either the address of an object of the same type, the value of another pointer, or a NULL constant.

Assigning an address to a pointer has already been discussed in the previous topic. To get the address of an object, use the & operation:

Int a = 10; int *pa = // pointer pa stores the address of variable a

Moreover, the pointer and variable must have the same type, in in this case int.

Assigning a pointer to another pointer:

#include int main(void) ( int a = 10; int b = 2; int *pa = int *pb = printf("Variable a: address=%p \t value=%d \n", pa, *pa); printf("Variable b: address=%p \t value=%d \n", pb, *pb); pa = pb; // now the pa pointer stores the address of variable b printf("Variable b: address=%p \ t value=%d \n", pa, *pa); return 0; )

When a pointer is assigned to another pointer, the first pointer actually begins to also point to the same address that the second pointer points to.

If we do not want the pointer to point to a specific address, we can assign it a conditional null value using the NULL constant, which is defined in the stdio.h header file:

Int *pa = NULL;

Pointer dereference

The operation of dereferencing a pointer in the form *pointer_name allows you to get an object at the address that is stored in the pointer.

#include int main(void) ( int a = 10; int *pa = int *pb = pa; *pa = 25; printf("Value on pointer pa: %d \n", *pa); // 25 printf(" Value on pointer pb: %d \n", *pb); // 25 printf("Value of variable a: %d \n", a); // 25 return 0; )

Through the expression *pa we can get the value at the address that is stored in the pa pointer, and through an expression like *pa = value we can insert a new value at this address.

And since in this case the pointer pa points to the variable a, then when the value at the address pointed to by the pointer changes, the value of the variable a will also change.

Pointer address

A pointer stores the address of a variable, and from this address we can get the value of that variable. But in addition, a pointer, like any variable, itself has an address where it is located in memory. This address can also be obtained using the & operation:

Int a = 10; int *pa = printf("address of pointer=%p \n", &pa); // pointer address printf("address stored in pointer=%p \n", pa); // the address that is stored in the pointer is the address of the variable a printf("value on pointer=%d \n", *pa); // value at the address in the pointer - the value of the variable a

Comparison Operations

Comparison operations > , >= , can be applied to pointers< , <= ,== , != . Операции сравнения применяются только к указателям одного типа и константе NULL . Для сравнения используются номера адресов:

Int a = 10; int b = 20; int *pa = int *pb = if(pa > pb) printf("pa (%p) is greater than pb (%p) \n", pa, pb); else printf("pa (%p) is less or equal pb (%p) \n", pa, pb);

Console output in my case:

Pa (0060FEA4) is greater than pb (0060FEA0)

Cast

Sometimes you need to assign a pointer of one type to the value of a pointer of another type. In this case, you should perform a type conversion operation:

Char c = "N"; char *pc = int *pd = (int *)pc; printf("pc=%p \n", pc); printf("pd=%p\n", pd);

When learning C, beginners often have questions related to pointers, I think everyone has approximately the same questions, so I will describe those that arose for me.

What is a pointer for?

Why is it always written “type pointer” and what is a type pointer uint16_t different from type pointer uint8_t?

And who came up with the index anyway?

Before answering these questions, let's remember what a pointer is.
A pointer is a variable that contains the address of some data element (variable, constant, function, structure).

To declare a variable as a pointer, you must precede its name with * , and to obtain the address of a variable it is used & (unary address operator).
char a = "a"; char *p =
In this case, p contains the address of variable a. But what's interesting is for further work with the pointer, you do not need to write an asterisk, it is only needed when declaring.
char a = "a"; char b = "b"; char *p = p =
In this case, p contains the address of the variable b, but if we want to get the value located at this address, then we need to use the dereference operator, same asterisk *.
char new_simbol = 0; char a = "a"; char *p = new_simbol = *p;
Thus, the new_simbol variable will contain the ascii code of the symbol "a".

Now let's move on directly to the questions of what the pointer is needed for. Imagine that we have an array that we want to work with in a function. In order to pass an array to a function, you need to copy it, that is, waste memory, which the MK already has little of, so a more correct solution would be not to copy the array, but to pass the address of its first element and size.
m =(1,2,3...);
You can do it like this
void foo(char *m, uint8_t size) ( )
or so
void foo(char m, uint8_t size) ( )
Since the name of an array contains the address of its first element, it is nothing more than a pointer. You can navigate through an array using the simplest arithmetic operations, for example, in order to get the value of the fifth element of an array, you need to add 4 to the array address (the address of the first element) and apply the dereference operator.
m = *(m + 4);

And the question immediately arises, why do they write the type before the pointer everywhere? Everything is simple, passing the address of the first element of the array and the size of the array, we say: From here (pointer) dig 10 holes (size of the array), we arrive in two hours, and those who were supposed to dig the holes called a tractor and are digging a pit. To avoid getting into this situation, it was necessary to specify the size of the hole; in our analogy, the type of the pointer is the same as the type determines how many bytes a variable will occupy in memory.

Thus, by specifying the pointer type, we tell the compiler, here is the address of the beginning of the array, one element of the array takes 2 bytes, there are 10 such elements in the array, so how much memory should we allocate for this array? 20 bytes - the compiler responds. For clarity, let's take a pointer of type void, it is not defined how much space it takes up- this is just an address, let's convert it to pointers different types and perform the deaddressing operation.


You can also pass a pointer to a structure to the function. Since the markup of the structure is known, we only need to pass the address of its beginning, and the compiler itself will break it into fields.

Well, the last question is who came up with this pointer. In order to understand this issue, we need to turn to an assembler, for example AVR, and there we will find instructions
st X, r1 ;save the contents of r1 in SRAM at address X, where X is a pair of registers r26, r27 ld r1,X ; load the contents of SRAM into r1 at address X, where X is a pair of registers r26, r27
It becomes clear that X contains pointer(address) and, it turns out, there is no evil guy who came up with a pointer to fool everyone, work with pointers (addresses) is supported at the MK kernel level.

Pointers are extremely powerful tool in programming. With the help of pointers, some things in programming can be made much easier and at the same timeThe efficiency of your program will increase significantly. Pointers even allow you to process an unlimited amount of data. For example, you can use pointers to change the values ​​of variables within a function, with the variables being passed to the function as parameters. Additionally, pointers can be used to dynamically allocate memory, which means you can write programs that can process virtually unlimited amounts of data on the fly—you don't need to know when you write the program how much memory to allocate in advance. This is perhaps the most powerful feature of pointers. First, let's just get a general understanding of pointers, learn how to declare and use them.

What are pointers and why are they needed?

Pointers are like labels that refer to locations in memory. Imagine a safe with different sized deposit boxes at your local bank. Each cell has a number, a unique number that is associated only with that cell, so you can quickly identify the desired cell. These numbers are similar to the addresses of computer memory cells. For example, you have a rich uncle who keeps all his valuables in his safe. And in order to secure all his savings, he decided to have a smaller safe, in which he would put a card that showed the location of the large safe and indicated the 16th password for the large safe, in which the real jewelry was stored. Essentially, a safe with a map will store the location of another safe. This whole jewel-saving organization is equivalent to pointers in C. In a computer, pointers are simply variables that store memory addresses, usually the addresses of other variables.

The idea is that knowing the address of a variable, you can go to that address and get the data stored in it. If you need to pass a huge chunk of data into a function, it's much easier to pass the memory address where that data is stored than to copy every piece of data! Moreover, if the program needs more memory, you can request more memory from the system. How does it work? The system simply returns the address of the memory location and we must store this address in a pointer variable. This way we can interact with data from the specified memory location.

Pointer syntax

If we have a pointer, then we can get its address in memory and the data it refers to, for this reason pointers have a somewhat unusual syntax that differs from simple variable declarations. Moreover, since pointers are not ordinary variables, you must tell the compiler that the variable is a pointer and tell the compiler the type of data that the pointer refers to. So the pointer is declared like this:

Data_type *pointerName;

where data_type is the data type, pointerName is the name of the pointer.

For example, let's declare a pointer that stores the address of a memory cell containing an integer:

Int *integerPointer;

Note the use of the * symbol when declaring a pointer. This character is the key character in the pointer declaration. If you add this symbol in a variable declaration, immediately before the variable name, the variable will be declared as a pointer. Additionally, if you declare multiple pointers on the same line, each one must be preceded by an asterisk character. Let's look at a few examples:

// Declaration of a pointer and a simple variable in one line int *pointer1, // this is a pointer variable; // this is a regular variable of type int // Declaration of two pointers in one line int *pointer1, // this is a pointer named pointer1 *pointer2; // this is a pointer named pointer2

As I said, if the variable name is not preceded by the * symbol, then it is a regular variable, otherwise it is a pointer. This is exactly what the example of pointer declarations above shows.

There are two ways to use a pointer:

  1. Use the pointer name without the * symbol, this way you can get the actual address of the memory location where the pointer refers.
  2. Use the pointer name with the * symbol to get the value stored in memory. In the context of pointers, the * symbol has a technical name: the dereference operation. Essentially we are taking a reference to some memory address to get the actual value. This may be difficult to understand, but in the future we will try to understand all this.

Declaring a pointer, obtaining the address of a variable

In order to declare a pointer that will refer to a variable, you must first obtain the address of that variable. To get the memory address of a variable (its location in memory), you need to use the & sign before the variable name. This allows you to find out the address of the memory cell in which the value of the variable is stored. This operation is called the address taking operation and looks like this:

Int var = 5; // simple declaration of a variable with preliminary initialization int *ptrVar; // declared a pointer, but it doesn't point to anything yet ptrVar = // now our pointer refers to the address in memory where the number 5 is stored

IN line 3 the address take operation was used, we took the address of the var variable and assigned it to the ptrVar pointer. Let's look at a program that will clearly show the power of pointers. So here's the source:

#include int main() ( int var; // regular integer variable int *ptrVar; // integer pointer (ptrVar must be of type int, since it will refer to a variable of type int) ptrVar = // assigned the pointer the address of the memory location where lies the value of the variable var scanf("%d", &var); // the variable var contains the value entered from the keyboard printf("%d\n", *ptrVar); // outputting the value through the pointer getchar(); )

Result of the program:

IN line 10, printf() prints the value stored in the var variable. Why is this happening? Well, let's look at the code. IN line 5, we declared a variable var of type int . IN line 6— pointer ptrVar to an integer value. Then the pointer ptrVar was assigned the address of the variable var, for this we used the address assignment operator. The user then enters a number which is stored in the var variable, remember this is the same location that ptrVar points to. Indeed, since we use the ampersand to assign a value to the var variable in scanf functions() , it should be clear that scanf() initializes the var variable via an address. The pointer ptrVar points to the same address.

Then, in line 10, the “dereferencing” operation is performed - *ptrVar. The program, through the ptrVar pointer, reads the address that is stored in the pointer, goes to the desired memory cell at the address, and returns the value that is stored there.

Note that in the example above, before the pointer can be used, it is first initialized to ensure that the pointer points to a specific memory address. If we started using the pointer without initializing it, it would refer to any memory location. And this could lead to extremely unpleasant consequences. For example, the operating system will likely prevent your program from accessing an unknown memory location because the OS knows that your program is not performing pointer initialization. Basically it just causes the program to crash.

If such tricks were allowed in the OS, you could access any memory location. And this means that for anyone running program you could make your own changes, for example, if you have a document open in Word, you could change any text programmatically. Fortunately, Windows and other modern OS will stop you from accessing that memory and will terminate your program prematurely.

Therefore, just remember, to avoid crashing your program, you should always initialize pointers before using them.

P.S.: If you don’t have money on your phone and there’s no way to top it up, but you urgently need to call, you can always use Beeline trust payment. Sum trust payment can be very varied, from 50 to 300 rubles.

Even if most programmers understand the difference between objects and pointers to them, sometimes it is not entirely clear which way to access an object should be chosen. Below we have tried to answer this question.

Question

I noticed that often programmers whose code I saw use pointers to objects more often than these objects themselves, i.e., for example, they use the following construction:

Object *myObject = new Object;

Object myObject;

Same with methods. Why instead:

MyObject.testFunc();

we should write this:

MyObject->testFunc();

As I understand it, this gives a gain in speed, because... we access memory directly. Right? P.S. I switched from Java.

Answer

Note, by the way, that in Java pointers are not used explicitly, i.e. A programmer cannot access an object in code through a pointer to it. However, in reality, in Java, all types, except base ones, are reference types: they are accessed by reference, although it is impossible to explicitly pass a parameter by reference. And also, note, new in C++ and in Java or C# are completely different things.

To give a little idea of ​​what pointers are in C++, here are two similar code fragments:

Object object1 = new Object(); // New object Object object2 = new Object(); // Another new object object1 = object2; // Both variables refer to the object previously referenced by object2 // If the object referenced by object1 changes, // object2 will also change, because they are the same object

The closest equivalent in C++ is:

Object * object1 = new Object(); // Memory is allocated for a new object // This memory is referenced by object1 Object * object2 = new Object(); // Same with the second object delete object1; // C++ does not have a garbage collection system, so if this is not done, // the program will no longer be able to access this memory, // at least until the program is restarted // This is called a memory leak object1 = object2; // As in Java, object1 points to the same place as object2

However, this is a completely different thing (C++):

Object object1; // New object Object object2; // Another object1 = object2; // Fully copying object2 to object1, // rather than redefining the pointer, is a very expensive operation

But will we gain speed by accessing memory directly?

Strictly speaking, this question combines two various issues. First: when should you use dynamic memory allocation? Second: when should you use pointers? Naturally, here we cannot do without general words that it is always necessary to choose the most suitable tool for the job. There is almost always a better implementation than using manual dynamic allocation (dynamic allocation) and/or raw pointers.

Dynamic distribution

The wording of the question presents two ways to create an object. And the main difference is their lifespan (storage duration) in program memory. Using Object myObject; , you rely on automatic lifetime detection, and the object will be destroyed as soon as it leaves its scope. But Object *myObject = new Object; keeps the object alive until you manually delete it from memory with the delete command. Use the latter option only when really necessary. And therefore Always choose to automatically determine the shelf life of an object if possible.

Typically, forced lifetime determination is used in the following situations:

  • You need the object to exist even after leaving its scope- exactly this object, exactly in this memory area, and not a copy of it. If this is not important to you (in most cases it is), rely on automatic lifetime determination. However, here's an example of a situation where you might need to access an object outside its scope, but you can do this without explicitly storing it: By writing an object to a vector, you can “break the connection” with the object itself - in fact it (and not a copy of it) will be available when called from a vector.
  • You need to use a lot of memory, which may overflow the stack. It’s great if you don’t have to deal with such a problem (and you rarely encounter it), because it’s “outside the competence” of C++, but unfortunately, sometimes you have to solve this problem too.
  • For example, you don’t know exactly the size of the array that you will have to use. As you know, in C++, arrays have a fixed size when defined. This can cause problems, for example, when reading user input. The pointer defines only the area in memory where the beginning of the array will be written, roughly speaking, without limiting its size.

If using dynamic allocation is necessary, then you should encapsulate it using a smart pointer (you can read in our article) or another type that supports the idiom “Obtaining a resource is initializing” (standard containers support this - this is the idiom according to which resource: block memory, file, network connection and so on. - when received, it is initialized in the constructor and then carefully destroyed by the destructor). Smart pointers are, for example, std::unique_ptr and std::shared_ptr .

Signposts

However, there are cases where the use of pointers is justified not only from a dynamic memory allocation point of view, but there is almost always an alternative way, without using pointers, which you should choose. As before, let's say: always opt for the alternative unless there is a particular need to use pointers.

Cases where the use of pointers can be considered as a possible option include the following:

  • Referential semantics. Sometimes it may be necessary to access an object (regardless of how memory is allocated for it), because you want to access functions in this object, and not its copy - i.e. when you need to implement pass by reference. However, in most cases, it is enough to use a link here, and not a pointer, because that is what links are created for. Note that these are slightly different things from what was described in point 1 above. But if you can access a copy of the object, then there is no need to use a reference (but note that copying an object is an expensive operation).
  • Polymorphism. Calling functions within polymorphism (dynamic object class) is possible using a reference or pointer. Again, using references is preferred.
  • Optional object. In this case, you can use nullptr to indicate that the object is omitted. If it's a function argument, then it's better to implement it with default arguments or an overload. Alternatively, you can use a type that encapsulates this behavior, such as boost::optional (modified in C++14 std::optional).
  • Improving compilation speed. You may need to separate compilation units (compilation units). One effective use of pointers is pre-declaration (because in order to use an object, you need to define it first). This will allow you to space out the compilation units, which can have a positive effect on speeding up compilation times, significantly reducing the time spent on this process.
  • Interaction with the libraryC or C-like. Here you will have to use raw pointers, freeing memory from them at the very last moment. You can get a raw pointer from a smart pointer, for example, with the get operation. If the library uses memory that must later be freed manually, you can frame the destructor in a smart pointer.