Error assignment of read only location

When I compile this program, I keep getting this error example4.c: In function ‘h’: example4.c:36: error: assignment of read-only location example4.c:37: error: assignment of read-only location I...

When I compile this program, I keep getting this error

example4.c: In function ‘h’:
example4.c:36: error: assignment of read-only location
example4.c:37: error: assignment of read-only location

I think it has something to do with the pointer. how do i go about fixing this. does it have to do with constant pointers being pointed to constant pointers?

code

#include <stdio.h>
#include <string.h>
#include "example4.h"

int main()
{
        Record value , *ptr;

        ptr = &value;

        value.x = 1;
        strcpy(value.s, "XYZ");

        f(ptr);
        printf("nValue of x %d", ptr -> x);
        printf("nValue of s %s", ptr->s);


        return 0;
}

void f(Record *r)
{
r->x *= 10;
        (*r).s[0] = 'A';
}

void g(Record r)
{
        r.x *= 100;
        r.s[0] = 'B';
}

void h(const Record r)
{
        r.x *= 1000;
        r.s[0] = 'C';
}

asked Apr 25, 2013 at 23:22

monkey doodle's user avatar

monkey doodlemonkey doodle

6705 gold badges12 silver badges21 bronze badges

3

In your function h you have declared that r is a copy of a constant Record — therefore, you cannot change r or any part of it — it’s constant.

Apply the right-left rule in reading it.

Note, too, that you are passing a copy of r to the function h() — if you want to modify r then you must pass a non-constant pointer.

void h( Record* r)
{
        r->x *= 1000;
        r->s[0] = 'C';
}

answered Apr 25, 2013 at 23:24

K Scott Piel's user avatar

0

Home »
C programs »
C common errors programs

Here, we are going to learn why an Error: Assignment of read-only location in C occurs and how to fixed in C programming language?

Submitted by IncludeHelp, on December 06, 2018

Error: assignment of read-only location occurs when we try to update/modify the value of a constant, because the value of a constant cannot be changed during the program execution, so we should take care about the constants. They are read-only.

Consider this example:

#include <stdio.h>

int main(void){
	//constant string (character pointer)
	const char *str = "Hello world";
	
	//changing first character
	str[0] ='p';  //will return error	
	printf("str: %sn",str);
	
	return 0;
}

Output

main.c: In function 'main':
main.c:8:9: error: assignment of read-only location '*str'
  str[0] ='p';  //will return error 
         ^

Here, the statement str[0]=’p’ will try to change the first character of the string, thus, the «Error: assignment of read-only location» will occur.

How to fix?

Do not change the value of a constant during program execution.

C Common Errors Programs »


  • Forum
  • Windows Programming
  • Compiler says «error: assignment of read

Compiler says «error: assignment of read-only location»

I have made a program that uses 3 classes that each do a specific task. The base class is used to declare 2 pure virtual functions that are instantiated in the other 2 derived classes.

One derived class uses one virtual function to get an entire copy of a file and store it in a c-string array, and the other derived class uses the second virtual function to «transform» all letters in the file to upper case (and store it in a c-string array). But the compiler says that there is an error «assignment of read-only location».

I have been trying to correct this problem but i can’t seem to actually correct it. Any help is appreciated.

P.S the program is not finished

Here is the driver program that tests all the classes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
#include <fstream>
#include "Transform class.h"
#include "OriginalFile class.h"

using namespace std;

int main ()
{


    fstream file;
    string name;

    cout << "Please type in a name of a file" << endl;
    cin >> name;

    file.open (name.c_str() , ios::in | ios::out | ios::app);

}

This is a derived class that is derived from the «FileFilter class»

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#define TRANSFORM CLASS_H
#include "FileFilter class.h"
#include <fstream>
using namespace std;

class Transform : public FileFilter
{
   char lines [1000];

public:

    virtual char transform (char, fstream &) const;



};

char Transform::transform (char ch, fstream &file) const
{
        int n = 0;
        int m = 0;
        int b = 0;
        char byte;


        while (!file.eof())
        {
            file.get (byte);
            lines[n] = byte;     //assignment of read only location?
            n++;
        }

        file.seekg (0, ios::beg);

        while (!file.eof())
        {
            lines[m] = toupper(lines[m]);
            m++;
        }

        file.seekg (0, ios::beg);


        while (!file.eof())
        {
            cout << lines[b];
            b++;
        }
}

This is another class that is derived from the «FileFilter» class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <fstream>
#define FILE FILTER_H
#define ORIGINALFILE CLASS_H
#include "FileFilter class.h"
using namespace std;

class OriginalFile : public FileFilter
{
protected:

    char lines_ [1000];


public:

    virtual void readFile (fstream &) const;


};


void OriginalFile::readFile (fstream & file1) const
{
        int n = 0;
        int m = 0;
        char byte;

        while (!file1.eof())
        {
            file1.get (byte);
            lines_[n] = byte;
            n++;
        }

        file1.seekg (0, ios::beg);

         while (!file1.eof())
        {
            cout << lines_[m] << endl;
            m++;
        }
}

And here is the base class

1
2
3
4
5
6
7
8
9
10
11
12
13
#define FILEFILTER CLASS_H
#include <fstream>

using namespace std;

class FileFilter
{
public:

    virtual char transform (char, fstream &) = 0;
    virtual void readFile (fstream &) = 0;

};

Also, the compiler says «error: redefinition of FileFilter class» But i dont see where i have redefined the filefiler class..

Last edited on

But the compiler says that there is an error «assignment of read-only location».

Exact error message please.

looks like you need to guard your headers….

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#ifndef FILEFILER_CLASS_H            <<<<<<<<<<< add this

#define FILEFILTER CLASS_H
#include <fstream>

using namespace std;

class FileFilter
{
public:

    virtual char transform (char, fstream &) = 0;
    virtual void readFile (fstream &) = 0;

};

#endif                  <<<<<<<< and this 

alternatively, if you are using MSVC you can pragma it instead.

1
2
3
4
5
6
7
8
9
10
11
12
13
#pragma once
#include <fstream>

using namespace std;

class FileFilter
{
public:

    virtual char transform (char, fstream &) = 0;
    virtual void readFile (fstream &) = 0;

};

oh, and as you will find, each derived class MUST implement all pure virtuals of the base class, you can’t pick and choose.

Last edited on

Topic archived. No new replies allowed.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
enum { finish = 0, start = 300, wall = 9999, empty_cell = -1 };
typedef std::vector< std::vector< int > > dim;
typedef std::pair< int, int > coord;
typedef std::vector< coord > way;
 
class Wave
{   
    public:
        Wave( const int _W, const int _H );
        
        void setStartPosition( const int X, const int Y );
        void setEndPosition( const int X, const int Y );
        
        dim generateMap() const;
        void setMap( dim src );
        
        way getWay() const;
        dim getMap() const;
        
    private:
        const int W;
        const int H;
        
        coord start_position;
        coord finish_position;
        dim field;
};
 
Wave::Wave( const int _W, const int _H ) : W( _W ), H( _H ) {}
 
void Wave::setStartPosition( const int X, const int Y ) {
    start_position.first = X;
    start_position.second = Y;
}
 
void Wave::setEndPosition( const int X, const int Y ) {
    finish_position.first = X;
    finish_position.second = Y;
}
 
void Wave::setMap( dim src ) {
    field = src;
}
 
dim Wave::getMap() const {
    return field; 
}
 
dim Wave::generateMap() const {
    dim temp;
    std::vector< int > row;
    
    for( int y = 0; y < H; y++ ) {
        row.clear();
        int val;
        for( int x = 0; x < W; x++ ) {
            val = rand();
            
            if( val % 7 == 0) 
                val = wall;
            else
                val = empty_cell;
            
            row.push_back( val );
        }
        temp.push_back( row );
    }
    
    return temp;
}
 
way Wave::getWay() const {
    way oldWave;
    way wave;
    oldWave.push_back( coord(1, 1) );
    int nstep = 0;
    
    
    field[ start_position.first ][ start_position.second ] = nstep; //а вот здесь и ругается мол
                                                                                    //field константный, но почему я не знаю
    const int dx[] = { 0, 1, 0, -1 };
    const int dy[] = { -1, 0, 1, 0 };
    
    while( oldWave.size() > 0 ) {
        ++nstep;
        wave.clear();
        for( way::iterator i = oldWave.begin(); i != oldWave.end(); ++i ) {
            for( int d = 0; d < 4; ++d ) {
                int nx = i->first + dx[d];
                int ny = i->second + dy[d];
                if( field[nx][ny] == empty_cell ) {
                    wave.push_back( coord(nx, ny) );
                    field[nx][ny] = nstep;
                    if (nx == finish_position.first && ny == finish_position.second )
                        goto done;
                }
            }
        }
        oldWave = wave;
    }
 done:
 
    int x = finish_position.first;
    int y = finish_position.second;
    wave.clear();
    wave.push_back( coord(x, y) );
    
    while( field[x][y] != 0 ) {
        for( int d = 0; d < 4; ++d ) {
            int nx = x + dx[d];
            int ny = y + dy[d];
            if( field[x][y] - 1 == field[nx][ny] ) {
                x = nx;
                y = ny;
                wave.push_back( coord(x, y) );
                break;
            }
        }
    }
    return wave;
}

Материал из Национальной библиотеки им. Н. Э. Баумана
Последнее изменение этой страницы: 16:36, 24 августа 2017.

Ошибка сегментации (англ. Segmentation fault, сокр. segfault, жарг. сегфолт) — ошибка программного обеспечения, возникающая при попытке обращения к недоступным для записи участкам памяти либо при попытке изменения памяти запрещённым способом.

Сегментная адресация памяти является одним из подходов к управлению и защите памяти в операционной системе. Для большинства целей она была вытеснена страничной памятью, однако в документациях по традиции используют термин «Ошибка сегментации». Некоторые операционные системы до сих пор используют сегментацию на некоторых логических уровнях, а страничная память используется в качестве основной политики управления памятью.

Сегментная адресация памяти является одним из подходов к управлению и защите памяти в операционной системе. Для большинства целей она была вытеснена страничной памятью, однако в документациях по традиции используют термин «Ошибка сегментации». Некоторые операционные системы до сих пор используют сегментацию на некоторых логических уровнях, а страничная память используется в качестве основной политики управления памятью.

Общие понятия

Сегментная адресация памяти  — схема логической адресации памяти компьютера в архитектуре x86. Линейный адрес конкретной ячейки памяти, который в некоторых режимах работы процессора будет совпадать с физическим адресом, делится на две части: сегмент и смещение. Сегментом называется условно выделенная область адресного пространства определённого размера, а смещением — адрес ячейки памяти относительно начала сегмента. Базой сегмента называется линейный адрес (адрес относительно всего объёма памяти), который указывает на начало сегмента в адресном пространстве. В результате получается сегментный (логический) адрес, который соответствует линейному адресу база сегмента+смещение и который выставляется процессором на шину адреса.

Ошибка сегментации происходит , когда программа пытается получить доступ к  месту памяти, на которое она не имеет права доступа, или пытается получить доступ к ячейке памяти таким образом, каким не допускается условиями системы.

Термин «сегментация» применяется в разных сферах вычислительной техники. В контексте обсуждаемой темы, термин используется с 1950 года , и относится к адресное пространству программы. Таким образом, при попытке чтения за пределами адресного пространства программы или запись на фрагмент, предназначенного только для чтения сегмента адресного пространства, приводит к ошибке сегментации, отсюда и название.

В системах, использующих аппаратные средства сегментации памяти для обеспечения виртуальной памяти, сбой сегментации возникает, когда оборудование системы обнаруживает попытку программы обратиться к несуществующему сегменту, и пытается сослаться на место вне границ сегмента, или сослаться на место на которое у нее нет разрешений.

В системах , использующих только пейджинг недопустимая страница, как правило, ведет к сегментации, ошибки сегментации или ошибки страницы, в виду устройства алгоритмов системы виртуальной памяти.

Страницы — это области физической памяти фиксированного размера, мельчайшая и неделимая единица памяти, которой может оперировать ОС.

На аппаратном уровне, неисправность возникает по причине реагирования блока управления памяти (MMU) на неправомерный доступ к памяти.

На уровне операционной системы эта ошибка ловится и сигнал передается в блок «offending process», где эта ошибка обрабатывается:

  • В UNIX-подобных операционных системах процесс, обращающийся к недействительным участкам памяти, получает сигнал «SIGSEGV».
  • В Microsoft Windows, процесс, получающий доступ к недействительным участкам памяти, создаёт исключение «STATUS_ACCESS_VIOLATION», и, как правило, предлагает запустить отладчик приложения Dr. Watson, которая показывает пользователю окно с предложением отправить отчет об ошибке Microsoft.

Суммирую можно сказать, когда пользовательский процесс хочет обратиться к памяти, то он просит MMU переадресовать его. Но если полученный адрес ошибочен, — находится вне пределов физического сегмента, или если сегмент не имеет нужных прав (попытка записи в read only-сегмент), — то ОС по умолчанию отправляет сигнал SIGSEGV, что приводит к прерыванию выполнения процесса и выдаче сообщения “segmentation fault”.

Причины сегментации

Наиболее распространенные причины ошибки сегментации:

  • Разыменование нулевых указателей 
  • Попытка доступа к несуществующему адресу памяти (за пределами адресного пространства процесса)
  • Попытка доступа к памяти программой, которая не имеет права на эту часть памяти
  • Попытка записи в память, предназначенной только для чтения

Причины зачастую вызваны ошибками программирования, которые приводят к ошибкам связанными с памятью:

  • Создание операций с разыменованым указателем или создание неинициализированного указателя (указатель, который указывает на случайный адрес памяти)
  • Разыменование или возложение на освобожденной указатель (использование оборванного указателя, который указывает на память, которая была освобождена или перераспределена).
  • Переполнение буфера
  • Переполнение стека
  • Попытка выполнить программу, которая не компилируется правильно. (Некоторые компиляторы будут выводить исполняемый файл , несмотря на наличие ошибок во время компиляции.)

Пример Segmentation Fault

Рассмотрим пример кода на ANSI C, который приводит к ошибке сегментации вследствие присутствия квалификатора Сonst — type:

 const char *s = "hello world";
 *(char *)s = 'H';

Когда программа, содержащая этот код, скомпилирована, строка «hello world» размещена в секции программы с бинарной пометкой «только для чтения». При запуске операционная система помещает её с другими строками и константами в сегмент памяти, предназначенный только для чтения. После запуска переменная s указывает на адрес строки, а попытка присвоить значение символьной константы H через переменную в памяти приводит к ошибке сегментации.

Компиляция и запуск таких программ на OpenBSD 4.0 вызывает следующую ошибку выполнения:

 
$ gcc segfault.c -g -o segfault
$ ./segfault
 Segmentation fault

Вывод отладчика gdb:

 Program received signal SIGSEGV, Segmentation fault.
 0x1c0005c2 in main () at segfault.c:6
 6               *s = 'H';

В отличие от этого, gcc 4.1.1 на GNU/Linux возвращает ошибку ещё во время компиляции:

 $ gcc segfault.c -g -o segfault
 segfault.c: In function ‘main’:
 segfault.c:4: error: assignment of read-only location

Условия, при которых происходят нарушения сегментации, и способы их проявления зависят от операционной системы.
Этот пример кода создаёт нулевой указатель и пытается присвоить значение по несуществующему адресу. Это вызывает ошибки сегментации во время выполнения программы на многих системах.

 int* ptr = (int*)0;
 *ptr = 1;

Ещё один способ вызвать ошибку сегментации заключается в том, чтобы вызвать функцию main рекурсивно, что приведёт к переполнению стека:

Обычно, ошибка сегментации происходит потому что: указатель или нулевой, или указывает на произвольный участок памяти (возможно, потому что не был инициализирован), или указывает на удаленный участок памяти. Например:

 char* p1 = NULL;  /* инициализирован как нулевой, в чем нет ничего плохого, но на многих системах он не может быть разыменован */
 char* p2;  /* вообще не инициализирован */
 char* p3  = (char *)malloc(20);  /* хорошо, участок памяти выделен */
 free(p3);  /* но теперь его больше нет */

Теперь разыменование любого из этих указателей может вызвать ошибку сегментации.
Также, при использовании массивов, если случайно указать в качестве размера массива неинициализированную переменную. Вот пример:

 int main()
 { 
     int const nmax=10;
     int i,n,a[n];
 }

Такая ошибка не прослеживается G++ при компоновке, но при запуске приложения вызовет ошибку сегментации.

Видеопример Segmentation Fault на примере C:

Источники

  • wiki info about Segmentation Fault
  • wiki Segmentation_fault
  • Почему возникает ошибка сегментации
  • Segmentation Fault
  • v
  • t
  • e

Операционные системы

Общая информация
  • Сравнение
  • Разработка криминалистического ПО
  • История
  • Список
  • Хронология
  • Доля использования
Ядро

Архитектура

  • Экзо
  • Гибридное
  • Микро
  • Монолитное
  • Нано

Компоненты

  • Драйвер
  • LKM
  • Микроядро
  • Пространство пользователя
Управление процессами

Аспекты

  • Переключение контекста
  • Прерывание
  • IPC
  • Процесс
  • Блок управления процессом
  • RTOS
  • Поток
  • Разделение времени

Планировщик
задач

  • Многозадачность
  • Упреждающее планирование с фиксированным приоритетом
  • Многоуровневые очереди с обратной связью
  • Вытесняющая многозадачность
  • Round-robin
  • SJN (Shortest job next)
Управление памятью
  • Ошибка на шине
  • Общая ошибка защиты
  • Защита памяти
  • Подкачка страниц
  • Кольца защиты
  • Ошибка сегментации
  • Виртуальная память
  • Страничная память
  • Сегментная адресация памяти
Память для хранения
Файловые системы
  • Загрузчик
  • Дефрагментация
  • Файл устройства
  • Атрибут файла
  • Inode
  • Журнал
  • Раздел диска
  • Виртуальная файловая система
  • VTL
Список
  • AmigaOS
  • Android
  • BeOS
  • BSD
  • Chrome OS
  • Cosmos
  • CP/M
  • Darwin
  • DOS
  • Genode
  • GNU
  • Haiku
  • illumos
  • IncludeOS
  • iOS
    • watchOS
    • tvOS
    • audioOS
  • ITS
  • Linux
  • Mac OS
    • Classic Mac OS
    • macOS
  • MINIX
  • MorphOS
  • MUSIC/SP
  • Nemesis
  • NeXTSTEP
  • NOS
  • OpenVMS
  • ORVYL
  • OS/2
  • OS-9
  • OSv
  • Pick
  • QNX
  • ReactOS
  • RISC OS
  • RSTS/E
  • RSX-11
  • RT-11
  • Solaris
  • TOPS-10/TOPS-20
  • z/TPF
  • TRIPOS
  • UNIX
  • Visi On
  • VM/CMS
  • VS/9
  • webOS
  • Windows
  • Xinu
  • z/OS
Прочее
  • API
  • Computer network
  • HAL
  • Live CD
  • Live USB
  • OS shell
    • CLI
    • GUI
    • NUI
    • TUI
    • VUI
    • ZUI
  • PXE

Вступление

Указатель — это тип переменной, который может хранить адрес другого объекта или функции.

Синтаксис

  • <Тип данных> * <Имя переменной>;
  • int * ptrToInt;
  • void * ptrToVoid; / * C89 + * /
  • struct someStruct * ptrToStruct;
  • int ** ptrToPtrToInt;
  • int arr [длина]; int * ptrToFirstElem = arr; / * Для <C99 ‘length’ должна быть константа времени компиляции, для> = C11 она может быть одной. * /
  • int * arrayOfPtrsToInt [длина]; / * Для <C99 ‘length’ должна быть константа времени компиляции, для> = C11 она может быть одной. * /

замечания

Позиция звездочки не влияет на смысл определения:

/* The * operator binds to right and therefore these are all equivalent. */
int *i;
int * i;
int* i;

Однако при определении нескольких указателей сразу требуется каждая из них:

int *i, *j; /* i and j are both pointers */
int* i, j;  /* i is a pointer, but j is an int not a pointer variable */

Также возможен массив указателей, где перед именем переменной массива задается звездочка:

int *foo[2]; /* foo is a array of pointers, can be accessed as *foo[0] and *foo[1] */

Общие ошибки

Неправильное использование указателей часто является источником ошибок, которые могут включать ошибки безопасности или сбои программ, чаще всего из-за сбоев сегментации.

Не проверять наличие сбоев

Выделение памяти не гарантируется, и вместо этого может возвращать указатель NULL . Используя возвращаемое значение, не проверяя, выполнено ли выделение, вызывает неопределенное поведение . Это обычно приводит к сбою, но нет никакой гарантии, что произойдет сбой, поэтому полагаться на это также может привести к проблемам.

Например, небезопасный способ:

struct SomeStruct *s = malloc(sizeof *s);
s->someValue = 0; /* UNSAFE, because s might be a null pointer */

Безопасный способ:

struct SomeStruct *s = malloc(sizeof *s);
if (s)
{
    s->someValue = 0; /* This is safe, we have checked that s is valid */
}

Использование литеральных чисел вместо sizeof при запросе памяти

Для конкретной конфигурации компилятора / машины типы имеют известный размер; однако нет никакого стандарта, который определяет, что размер данного типа (кроме char ) будет одинаковым для всех конфигураций компилятора / машины. Если код использует 4 вместо sizeof(int) для распределения памяти, он может работать на исходном компьютере, но код не обязательно переносится на другие машины или компиляторы. Фиксированные размеры для типов должны быть заменены sizeof(that_type) или sizeof(*var_ptr_to_that_type) .

Не переносное распределение:

 int *intPtr = malloc(4*1000);    /* allocating storage for 1000 int */
 long *longPtr = malloc(8*1000);  /* allocating storage for 1000 long */

Переносное распределение:

 int *intPtr = malloc(sizeof(int)*1000);     /* allocating storage for 1000 int */
 long *longPtr = malloc(sizeof(long)*1000);  /* allocating storage for 1000 long */

Или, еще лучше:

 int *intPtr = malloc(sizeof(*intPtr)*1000);     /* allocating storage for 1000 int */
 long *longPtr = malloc(sizeof(*longPtr)*1000);  /* allocating storage for 1000 long */

Утечка памяти

Невыполнение выделенной памяти с помощью free ведет к созданию памяти без повторного использования, которая больше не используется программой; это называется утечкой памяти . Память утечки ресурсов памяти отходов и может привести к сбоям распределения.

Логические ошибки

Все распределения должны соответствовать одному и тому же шаблону:

  1. Выделение с использованием malloc (или calloc )
  2. Использование для хранения данных
  3. Де-распределение с использованием free

Несоблюдение этого шаблона, например, использование памяти после вызова free ( висячего указателя ) или перед вызовом malloc ( дикий указатель ), вызов free дважды («double free») и т. Д. Обычно вызывает ошибку сегментации и приводит к краху программы.

Эти ошибки могут быть временными и трудно отлаживать — например, освобожденная память обычно не сразу восстанавливается ОС, и поэтому оборванные указатели могут сохраняться некоторое время и, похоже, работают.

В системах, где это работает, Valgrind — бесценный инструмент для определения того, какая память просочилась и где она была изначально выделена.

Создание указателей на стек переменных

Создание указателя не продлевает срок действия указанной переменной. Например:

int* myFunction() 
{
    int x = 10;
    return &x;
}

Здесь x имеет время автоматического хранения (обычно называемое распределением стека ). Поскольку он выделяется в стеке, его время жизни остается до тех пор, пока выполняется myFunction ; после выхода myFunction переменная x будет уничтожена. Эта функция получает адрес x (с использованием &x ) и возвращает его вызывающему, оставляя вызывающего абонента указателем на несуществующую переменную. Попытка доступа к этой переменной затем вызовет неопределенное поведение .

Большинство компиляторов фактически не очищают кадр стека после выхода функции, поэтому разыменование возвращаемого указателя часто дает вам ожидаемые данные. Однако при вызове другой функции указываемая память может быть перезаписана, и, похоже, поврежденные данные были повреждены.

Чтобы устранить это, либо malloc хранилище для возвращаемой переменной, либо верните указатель на вновь созданное хранилище, либо потребуйте, чтобы действительный указатель был передан функции вместо того, чтобы возвращать ее, например:

#include <stdlib.h>
#include <stdio.h>

int *solution1(void) 
{
    int *x = malloc(sizeof *x);
    if (x == NULL) 
    {
        /* Something went wrong */
        return NULL;
    }

    *x = 10;

    return x;
}

void solution2(int *x) 
{
    /* NB: calling this function with an invalid or null pointer 
       causes undefined behaviour. */

    *x = 10;
}

int main(void) 
{
    { 
        /* Use solution1() */

        int *foo = solution1();  
        if (foo == NULL)
        {
            /* Something went wrong */
            return 1;
        }

        printf("The value set by solution1() is %in", *foo);
        /* Will output: "The value set by solution1() is 10" */

        free(foo);    /* Tidy up */
    }

    {
        /* Use solution2() */

        int bar;
        solution2(&bar); 

        printf("The value set by solution2() is %in", bar);
        /* Will output: "The value set by solution2() is 10" */
    }

    return 0;
}

Приращение / декремент и разыменование

Если вы пишете *p++ чтобы увеличить то, что указано p , вы ошибаетесь.

Приращение / декрементирование сообщения выполняется до разыменования. Поэтому это выражение будет увеличивать указатель p и возвращать то, что было указано p до того, как он увеличился, не меняя его.

Вы должны написать (*p)++ для увеличения того, на что указывает p .

Это правило также применяется к пост декрементированию: *p-- будет декрементировать указатель p , а не то, что указано p .

Выделение указателя

int a = 1;
int *a_pointer = &a;

Чтобы разыменовать a_pointer и изменить значение a, мы используем следующую операцию

*a_pointer = 2;

Это можно проверить, используя следующие операторы печати.

printf("%dn", a); /* Prints 2 */
printf("%dn", *a_pointer); /* Also prints 2 */

Однако можно было бы ошибаться, чтобы разыменовать NULL или иначе недействительный указатель. это

int *p1, *p2;

p1 = (int *) 0xbad;
p2 = NULL;

*p1 = 42;
*p2 = *p1 + 1;

обычно является неопределенным поведением . p1 не может быть разыменован, поскольку он указывает на адрес 0xbad который не может быть действительным адресом. Кто знает, что там? Это может быть операционная память системы или память другой программы. Единственный временный код, подобный этому, используется во встроенной разработке, которая хранит определенную информацию по жестко закодированным адресам. p2 не может быть разыменован, поскольку он имеет NULL , что является недопустимым.

Выделение указателя на структуру

Допустим, у нас есть следующая структура:

struct MY_STRUCT 
{
    int my_int;
    float my_float;
};

Мы можем определить MY_STRUCT чтобы опустить ключевое слово struct поэтому нам не нужно набирать struct MY_STRUCT каждый раз, когда мы его используем. Это, однако, не является обязательным.

typedef struct MY_STRUCT MY_STRUCT;

Если у нас есть указатель на экземпляр этой структуры

MY_STRUCT *instance;

Если этот оператор появляется в области файлов, instance будет инициализирован нулевым указателем при запуске программы. Если этот оператор появляется внутри функции, его значение не определено. Переменная должна быть инициализирована, чтобы указывать на действительную переменную MY_STRUCT или на динамически распределенное пространство, прежде чем она может быть разыменована. Например:

MY_STRUCT info = { 1, 3.141593F };
MY_STRUCT *instance = &info;

Когда указатель действителен, мы можем разыменовать его для доступа к его членам с использованием одного из двух разных обозначений:

int a = (*instance).my_int;
float b = instance->my_float;

Хотя оба эти метода работают, лучше использовать оператор arrow -> а не комбинацию круглых скобок, оператора разыменования * и точки . потому что его легче читать и понимать, особенно с вложенными приложениями.

Другое важное различие показано ниже:

MY_STRUCT copy = *instance;
copy.my_int    = 2;

В этом случае copy содержит копию содержимого instance . Изменение my_int copy не изменит его в instance .

MY_STRUCT *ref = instance;
ref->my_int    = 2;

В этом случае ref является ссылкой на instance . Изменение my_int с использованием ссылки изменит его в instance .

Общепринятой практикой является использование указателей для структур как параметров в функциях, а не самих структур. Использование структур в качестве параметров функции может привести к переполнению стека, если структура велика. Использование указателя на структуру использует только достаточное пространство стека для указателя, но может вызвать побочные эффекты, если функция изменяет структуру, которая передается в функцию.

Указатели функций

Указатели также могут использоваться для указания функций.

Возьмем основную функцию:

int my_function(int a, int b)
{
    return 2 * a + 3 * b;
}

Теперь давайте определим указатель на тип этой функции:

int (*my_pointer)(int, int);

Чтобы создать его, просто используйте этот шаблон:

return_type_of_func (*my_func_pointer)(type_arg1, type_arg2, ...)

Затем мы должны назначить этот указатель на функцию:

my_pointer = &my_function;

Этот указатель теперь можно использовать для вызова функции:

/* Calling the pointed function */
int result = (*my_pointer)(4, 2);

...

/* Using the function pointer as an argument to another function */
void another_function(int (*another_pointer)(int, int))
{
    int a = 4;
    int b = 2;
    int result = (*another_pointer)(a, b);

    printf("%dn", result);
}

Хотя этот синтаксис кажется более естественным и согласованным с базовыми типами, указатели на функции присвоения и разыменования не требуют использования операторов & и * . Таким образом, следующий снипп одинаково справедлив:

/* Attribution without the & operator */
my_pointer = my_function;

/* Dereferencing without the * operator */
int result = my_pointer(4, 2);

Чтобы повысить читаемость указателей на функции, можно использовать typedefs.

typedef void (*Callback)(int a);

void some_function(Callback callback)
{
    int a = 4;
    callback(a);
}

Еще одна удобочитаемость заключается в том, что стандарт C позволяет упростить указатель функции в аргументах, подобных приведенным выше (но не в объявлении переменных), к тому, что выглядит как прототип функции; таким образом, для определений функций и деклараций можно эквивалентно использовать следующее:

void some_function(void callback(int))
{
    int a = 4;
    callback(a);
}

Смотрите также

Указатели функций

Инициализация указателей

Инициализация указателя — хороший способ избежать диких указателей. Инициализация проста и ничем не отличается от инициализации переменной.

#include <stddef.h>

int main()
{
    int *p1 = NULL; 
    char *p2 = NULL;
    float *p3 = NULL;

         /* NULL is a macro defined in stddef.h, stdio.h, stdlib.h, and string.h */

    ...
}    

В большинстве операционных систем, непреднамеренно используя указатель, который был инициализирован NULL , часто приводит к сбою программы немедленно, что позволяет легко определить причину проблемы. Использование неинициализированного указателя часто может вызывать затруднительные диагностические ошибки.

Внимание:

Результат разыменования указателя NULL не определен, поэтому он не обязательно приведет к сбою, даже если это естественное поведение операционной системы, в которой работает программа. Оптимизация компилятора может привести к сбою в сбое, вызвав возникновение сбоя до или после точки в исходном коде, в котором произошла ошибка разыменования нулевого указателя, или привести к неожиданному удалению частей кода, которые содержат разыменование нулевого указателя, из программы. Отладочные сборки обычно не будут демонстрировать это поведение, но это не гарантируется языковым стандартом. Также допускается другое неожиданное и / или нежелательное поведение.

Поскольку NULL никогда не указывает на переменную, на выделенную память или на функцию, ее безопасно использовать в качестве защитного значения.

Внимание:

Обычно NULL определяется как (void *)0 . Но это не означает, что назначенный адрес памяти равен 0x0 . Для получения дополнительной информации обратитесь к C-faq для NULL-указателей

Обратите внимание, что вы также можете инициализировать указатели, чтобы они содержали значения, отличные от NULL.

int i1;

int main()
{
   int *p1 = &i1;
   const char *p2 = "A constant string to point to";
   float *p3 = malloc(10 * sizeof(float));
}

Адрес-оператора (&)

Для любого объекта (т. Е. Переменной, массива, объединения, структуры, указателя или функции) оператор унарного адреса может использоваться для доступа к адресу этого объекта.

Предположим, что

int i = 1;              
int *p = NULL;

Итак, утверждение p = &i; , копирует адрес переменной i в указатель p .

Это выражается как p points to i .

printf("%dn", *p); prints 1, значение i .

Арифметика указателей

См. Здесь: Арифметика указателей

void * указатели в качестве аргументов и возвращаемые значения для стандартных функций

К & Р

void* — это тип catch для типов указателей на типы объектов. Примером этого является использование функции malloc , которая объявляется как

void* malloc(size_t);

Тип возвращаемого указателя в void означает, что можно присвоить возвращаемое значение из malloc указателю на любой другой тип объекта:

int* vector = malloc(10 * sizeof *vector);

Обычно считается хорошей практикой, чтобы явным образом не вводить значения в указатели void и из них. В конкретном случае malloc() это связано с тем, что при явном приведении компилятор может в противном случае предположить, но не предупреждать о некорректном возвращаемом типе для malloc() , если вы забыли включить stdlib.h . Это также случай использования правильного поведения указателей пустот, чтобы лучше соответствовать принципу DRY (не повторяйте себя); сравните это со следующим, в котором следующий код содержит несколько ненужных дополнительных мест, где опечатка может вызвать проблемы:

int* vector = (int*)malloc(10 * sizeof int*);

Аналогично, такие функции, как

void* memcpy(void *restrict target, void const *restrict source, size_t size);

имеют свои аргументы, указанные как void * потому что адрес любого объекта, независимо от типа, может быть передан. Здесь также вызов не должен использовать листинг

unsigned char buffer[sizeof(int)];
int b = 67;
memcpy(buffer, &b, sizeof buffer);

Концентраторы

Одиночные указатели

  • Указатель на int

    Указатель может указывать на разные целые числа, а int может быть изменен с помощью указателя. Этот образец кода присваивает b для указания на int b затем изменяет значение b на 100 .

    int b;
    int* p;
    p = &b;    /* OK */
    *p = 100;  /* OK */
    
  • Указатель на const int

    Указатель может указывать на разные целые числа, но значение int не может быть изменено с помощью указателя.

    int b;
    const int* p;
    p = &b;    /* OK */
    *p = 100;  /* Compiler Error */
    
  • const указатель на int

    Указатель может указывать только на один int но значение int может быть изменено с помощью указателя.

    int a, b;
    int* const p = &b; /* OK as initialisation, no assignment */
    *p = 100;  /* OK */
    p = &a;    /* Compiler Error */
    
  • const указатель на const int

    Указатель может указывать только на один int и int не может быть изменен с помощью указателя.

    int a, b;
    const int* const p = &b; /* OK as initialisation, no assignment */
    p = &a;   /* Compiler Error */
    *p = 100; /* Compiler Error */
    

Указатель на указатель

  • Указатель на указатель на int

    Этот код присваивает адрес p1 двойному указателю p (который затем указывает на int* p1 (который указывает на int )).

    Затем p1 на точку int a . Затем изменяется значение a равным 100.

    void f1(void)
    {
      int a, b;
      int *p1;
      int **p;
      p1 = &b; /* OK */
      p = &p1; /* OK */
      *p = &a; /* OK */
      **p = 100; /* OK */
    }
    
  • Указатель на указатель на const int

     void f2(void)
    {
      int b;
      const int *p1;
      const int **p;
      p = &p1; /* OK */
      *p = &b; /* OK */
      **p = 100; /* error: assignment of read-only location ‘**p’ */
    }
    
  • Указатель на указатель const на int

    void f3(void)
    {
      int b;
      int *p1;
      int * const *p;
      p = &p1; /* OK */
      *p = &b; /* error: assignment of read-only location ‘*p’ */
      **p = 100; /* OK */
    }
    
  • const указатель на указатель на int

    void f4(void)
    {
      int b;
      int *p1;
      int ** const p = &p1; /* OK as initialisation, not assignment */
      p = &p1; /* error: assignment of read-only variable ‘p’ */
      *p = &b; /* OK */
      **p = 100; /* OK */
    }
    
  • Указатель на const указатель на const int

    void f5(void)
    {
      int b;
      const int *p1;
      const int * const *p;
      p = &p1; /* OK */
      *p = &b; /* error: assignment of read-only location ‘*p’ */
      **p = 100; /* error: assignment of read-only location ‘**p’ */
    }
    
  • const указатель на указатель на const int

    void f6(void)
    {
      int b;
      const int *p1;
      const int ** const p = &p1; /* OK as initialisation, not assignment */
      p = &p1; /* error: assignment of read-only variable ‘p’ */
      *p = &b; /* OK */
      **p = 100; /* error: assignment of read-only location ‘**p’ */
    }
    
  • const указатель на const указатель на int

    void f7(void)
    {
      int b;
      int *p1;
      int * const * const p = &p1; /* OK as initialisation, not assignment */
      p = &p1; /* error: assignment of read-only variable ‘p’  */
      *p = &b; /* error: assignment of read-only location ‘*p’ */
      **p = 100; /* OK */
    }
    

Одинаковая звездочка, разные значения

посылка

Самая запутанная вещь, связанная с синтаксисом указателя на C и C ++, состоит в том, что на самом деле существуют два разных значения, которые применяются, когда символ указателя, звездочка ( * ) используется с переменной.

пример

Во-первых, вы используете * для объявления переменной указателя.

int i = 5;
/* 'p' is a pointer to an integer, initialized as NULL */
int *p = NULL;
/* '&i' evaluates into address of 'i', which then assigned to 'p' */
p = &i;
/* 'p' is now holding the address of 'i' */

Когда вы не объявляете (или не умножаете), * используется для разыменования переменной указателя:

*p = 123;
/* 'p' was pointing to 'i', so this changes value of 'i' to 123 */

Если вы хотите, чтобы существующая переменная указателя удерживала адрес другой переменной, вы не используете * , но делаете это следующим образом:

p = &another_variable;

Обычная путаница среди новичков C-программирования возникает, когда они объявляют и инициализируют переменную указателя одновременно.

int *p = &i;

Поскольку int i = 5; и int i; i = 5; дают тот же результат, некоторые из них могли бы считать int *p = &i; и int *p; *p = &i; дайте тот же результат тоже. Дело в том, что нет, int *p; *p = &i; будет пытаться уважать неинициализированный указатель, который приведет к UB. Никогда не используйте * если вы не декларируете и не разыскиваете указатель.

Заключение

Звездочка ( * ) имеет два различных значения внутри C относительно указателей, в зависимости от того, где они используются. При использовании в объявлении переменной значение в правой части равенства равно значению указателя на адрес в памяти. При использовании с уже объявленной переменной , звездочка будет разыменовывать значение указателя, следуя за ним в указанное место в памяти и позволяя присвоить или получить значение, которое будет там сохранено.

навынос

Важно помнить о своих P и Q, так сказать, при работе с указателями. Помните, когда вы используете звездочку, и что это означает, когда вы ее используете. Осмотр этой крошечной детали может привести к ошибкам и / или неопределенному поведению, с которыми вы действительно не хотите иметь дело.

Указатель на указатель

В C указатель может ссылаться на другой указатель.

#include <stdio.h>
#include <stdlib.h>

int main(void) {
  int A = 42;
  int* pA = &A;
  int** ppA = &pA;
  int*** pppA = &ppA;

  printf("%d", ***pppA); /* prints 42 */

  return EXIT_SUCCESS;
}

Но ссылки и ссылки напрямую не разрешены.

#include <stdio.h>
#include <stdlib.h>

int main(void) {
  int A = 42;
  int* pA = &A;
  int** ppA = &&A; /* Compilation error here! */
  int*** pppA = &&&A;  /* Compilation error here! */

  ...

Вступление

Указатель объявляется так же, как любая другая переменная, за исключением того, что звездочка ( * ) помещается между типом и именем переменной, чтобы обозначить это указатель.

int *pointer; /* inside a function, pointer is uninitialized and doesn't point to any valid object yet */

Чтобы объявить две переменные указателя того же типа, в том же объявлении, используйте символ звездочки перед каждым идентификатором. Например,

int *iptr1, *iptr2;
int *iptr3,  iptr4;  /* iptr3 is a pointer variable, whereas iptr4 is misnamed and is an int */

Оператор адреса или ссылки, обозначенный амперсандом ( & ), дает адрес данной переменной, который может быть помещен в указатель соответствующего типа.

int value = 1;
pointer = &value;

Оператор косвенности или разыменования, обозначенный звездочкой ( * ), получает содержимое объекта, на который указывает указатель.

printf("Value of pointed to integer: %dn", *pointer);
/* Value of pointed to integer: 1 */

Если указатель указывает на структуру или тип объединения, вы можете разыменовать его и получить доступ к его членам напрямую с помощью оператора -> :

SomeStruct *s = &someObject;
s->someMember = 5; /* Equivalent to (*s).someMember = 5 */

В C указатель представляет собой отдельный тип значения, который может быть переназначен и в противном случае рассматривается как переменная в своем собственном праве. Например, следующий пример печатает значение самого указателя (переменной).

printf("Value of the pointer itself: %pn", (void *)pointer);
/* Value of the pointer itself: 0x7ffcd41b06e4 */
/* This address will be different each time the program is executed */

Поскольку указатель является изменяемой переменной, он может не указывать на действительный объект, либо путем установки значения null

pointer = 0;     /* or alternatively */
pointer = NULL;

или просто путем размещения произвольного битового шаблона, который не является допустимым адресом. Последнее очень плохое, потому что он не может быть протестирован до того, как указатель будет разыменован, есть только тест для случая, когда указатель имеет значение null:

if (!pointer) exit(EXIT_FAILURE);

Указатель может быть разыменован только если он указывает на действительный объект, иначе поведение не определено. Многие современные реализации могут помочь вам, подняв некоторую ошибку, такую ​​как ошибка сегментации и прекратить выполнение, но другие могут просто оставить вашу программу в недопустимом состоянии.

Значение, возвращаемое оператором разыменования, является изменчивым псевдонимом исходной переменной, поэтому его можно изменить, изменив исходную переменную.

*pointer += 1;
printf("Value of pointed to variable after change: %dn", *pointer);
/* Value of pointed to variable after change: 2 */

Указатели также переустанавливаются. Это означает, что указатель, указывающий на объект, может впоследствии использоваться для указания на другой объект того же типа.

int value2 = 10;
pointer = &value2;
printf("Value from pointer: %dn", *pointer);
/* Value from pointer: 10 */

Как и любая другая переменная, указатели имеют определенный тип. Например, вы не можете назначить адрес short int указателю на long int . Такое поведение упоминается как тип punning и запрещено в C, хотя есть несколько исключений.

Хотя указатель должен иметь определенный тип, память, выделенная для каждого типа указателя, равна памяти, используемой средой для хранения адресов, а не размеру указанного типа.

#include <stdio.h>

int main(void) {
    printf("Size of int pointer: %zun", sizeof (int*));      /* size 4 bytes */
    printf("Size of int variable: %zun", sizeof (int));      /* size 4 bytes */
    printf("Size of char pointer: %zun", sizeof (char*));    /* size 4 bytes */
    printf("Size of char variable: %zun", sizeof (char));    /* size 1 bytes */
    printf("Size of short pointer: %zun", sizeof (short*));  /* size 4 bytes */
    printf("Size of short variable: %zun", sizeof (short));  /* size 2 bytes */
    return 0;
}

(NB: если вы используете Microsoft Visual Studio, которая не поддерживает стандарты C99 или C11, вы должны использовать %Iu 1 вместо %zu в приведенном выше примере.)

Обратите внимание, что приведенные выше результаты могут варьироваться в зависимости от окружения и окружающей среды, но все среды отображают одинаковые размеры для разных типов указателей.

Экстракт на основе информации из Кардиффского университета C Указатели Введение

Указатели и массивы

Указатели и массивы тесно связаны в C. Массивы в C всегда хранятся в смежных местах в памяти. Арифметика указателя всегда масштабируется по размеру указанного пункта. Поэтому, если у нас есть массив из трех двойников и указатель на базу, *ptr относится к первому двойнику *(ptr + 1) ко второму, *(ptr + 2) к третьему. Более удобная нотация — использовать нотацию массива [] .

double point[3] = {0.0, 1.0, 2.0};
double *ptr = point;

/* prints x 0.0, y 1.0 z 2.0 */ 
printf("x %f y %f z %fn", ptr[0], ptr[1], ptr[2]);

Таким образом, ptr и имя массива являются взаимозаменяемыми. Это правило также означает, что массив переходит в указатель при передаче в подпрограмму.

double point[3] = {0.0, 1.0, 2.0};

printf("length of point is %sn", length(point));

/* get the distance of a 3D point from the origin */ 
double length(double *pt)
{
   return sqrt(pt[0] * pt[0] + pt[1] * pt[1] + pt[2] * pt[2])
}

Указатель может указывать на любой элемент в массиве или на элемент за последним элементом. Однако ошибка заключается в установке указателя на любое другое значение, включая элемент перед массивом. (Причина в том, что на сегментированных архитектурах адрес перед первым элементом может пересекать границу сегмента, компилятор гарантирует, что это не произойдет для последнего элемента плюс один).


Сноска 1: информация о формате Microsoft может быть найдена с помощью printf() и синтаксиса спецификации формата .

Полиморфное поведение с указателями void

Стандартная библиотечная функция qsort() является хорошим примером того, как можно использовать указатели void, чтобы одна функция работала на множестве разных типов.

void qsort (
    void *base,                                 /* Array to be sorted */
    size_t num,                                 /* Number of elements in array */
    size_t size,                                /* Size in bytes of each element */
    int (*compar)(const void *, const void *)); /* Comparison function for two elements */

Массив, который нужно отсортировать, передается как указатель на void, поэтому можно использовать массив любого типа элемента. Следующие два аргумента qsort() сколько элементов он должен ожидать в массиве, и насколько велики в байтах каждый элемент.

Последний аргумент — это указатель на функцию сравнения, которая сама принимает два указателя void. Предоставляя вызывающей стороне эту функцию, qsort() может эффективно сортировать элементы любого типа.

Вот пример такой функции сравнения, для сравнения поплавков. Обратите внимание, что любая функция сравнения, переданная в qsort() должна иметь подпись этого типа. Способ, которым он сделан полиморфным, заключается в том, чтобы отличить аргументы указателя void от указателей типа элемента, который мы хотим сравнить.

int compare_floats(const void *a, const void *b)
{
    float fa = *((float *)a);
    float fb = *((float *)b);
    if (fa < fb)
        return -1;
    if (fa > fb)
        return 1;
    return 0;
}

Поскольку мы знаем, что qsort будет использовать эту функцию для сравнения float, мы передаем аргументы указателя void обратно в указатели float перед их разыменованием.

Теперь использование полиморфной функции qsort в массиве «array» с длиной «len» очень просто:

qsort(array, len, sizeof(array[0]), compare_floats);

ошибка: назначение местоположения только для чтения

Когда я компилирую эту программу, я продолжаю получать эту ошибку

example4.c: In function ‘h’:
example4.c:36: error: assignment of read-only location
example4.c:37: error: assignment of read-only location

Я думаю, что это как-то связано с указателем. как мне это исправить. это связано с постоянными указателями, указывающими на постоянные указатели?

код

#include <stdio.h>
#include <string.h>
#include "example4.h"

int main()
{
        Record value , *ptr;

        ptr = &value;

        value.x = 1;
        strcpy(value.s, "XYZ");

        f(ptr);
        printf("nValue of x %d", ptr -> x);
        printf("nValue of s %s", ptr->s);


        return 0;
}

void f(Record *r)
{
r->x *= 10;
        (*r).s[0] = 'A';
}

void g(Record r)
{
        r.x *= 100;
        r.s[0] = 'B';
}

void h(const Record r)
{
        r.x *= 1000;
        r.s[0] = 'C';
}

В вашей функции h вы заявили, что r является копией константы Record — следовательно, вы не можете изменить r или любая его часть — это постоянно.

Примените правило правой-левой стороны при чтении.

Обратите также внимание, что вы передаете копия of r к функции h() — если вы хотите изменить r то вы должны передать непостоянный указатель.

void h( Record* r)
{
        r->x *= 1000;
        r->s[0] = 'C';
}

ответ дан 26 апр.

Не тот ответ, который вы ищете? Просмотрите другие вопросы с метками

c
compiler-errors

or задайте свой вопрос.

Introduction

A pointer is a type of variable which can store the address of another object or a function.

Syntax

  • <Data type> *<Variable name>;
  • int *ptrToInt;
  • void *ptrToVoid; /* C89+ */
  • struct someStruct *ptrToStruct;
  • int **ptrToPtrToInt;
  • int arr[length]; int *ptrToFirstElem = arr; /* For <C99 ‘length’ needs to be a compile time constant, for >=C11 it might need to be one. */
  • int *arrayOfPtrsToInt[length]; /* For <C99 ‘length’ needs to be a compile time constant, for >=C11 it might need to be one. */

The position of the asterisk does not affect the meaning of the definition:

/* The * operator binds to right and therefore these are all equivalent. */
int *i;
int * i;
int* i;

However, when defining multiple pointers at once, each requires its own asterisk:

int *i, *j; /* i and j are both pointers */
int* i, j;  /* i is a pointer, but j is an int not a pointer variable */

An array of pointers is also possible, where an asterisk is given before the array variable’s name:

int *foo[2]; /* foo is a array of pointers, can be accessed as *foo[0] and *foo[1] */

Common errors

Improper use of pointers are frequently a source of bugs that can include security bugs or program crashes, most often due to segmentation faults.

Not checking for allocation failures

Memory allocation is not guaranteed to succeed, and may instead return a NULL pointer. Using the returned value, without checking if the allocation is successful, invokes undefined behavior. This usually leads to a crash, but there is no guarantee that a crash will happen so relying on that can also lead to problems.

For example, unsafe way:

struct SomeStruct *s = malloc(sizeof *s);
s->someValue = 0; /* UNSAFE, because s might be a null pointer */

Safe way:

struct SomeStruct *s = malloc(sizeof *s);
if (s)
{
    s->someValue = 0; /* This is safe, we have checked that s is valid */
}

Using literal numbers instead of sizeof when requesting memory

For a given compiler/machine configuration, types have a known size; however, there isn’t any standard which defines that the size of a given type (other than char) will be the same for all compiler/machine configurations. If the code uses 4 instead of sizeof(int) for memory allocation, it may work on the original machine, but the code isn’t necessarily portable to other machines or compilers. Fixed sizes for types should be replaced by sizeof(that_type) or sizeof(*var_ptr_to_that_type).

Non-portable allocation:

 int *intPtr = malloc(4*1000);    /* allocating storage for 1000 int */
 long *longPtr = malloc(8*1000);  /* allocating storage for 1000 long */

Portable allocation:

 int *intPtr = malloc(sizeof(int)*1000);     /* allocating storage for 1000 int */
 long *longPtr = malloc(sizeof(long)*1000);  /* allocating storage for 1000 long */

Or, better still:

 int *intPtr = malloc(sizeof(*intPtr)*1000);     /* allocating storage for 1000 int */
 long *longPtr = malloc(sizeof(*longPtr)*1000);  /* allocating storage for 1000 long */

Memory leaks

Failure to de-allocate memory using free leads to a buildup of non-reusable memory, which is no longer used by the program; this is called a memory leak. Memory leaks waste memory resources and can lead to allocation failures.

Logical errors

All allocations must follow the same pattern:

  1. Allocation using malloc (or calloc)
  2. Usage to store data
  3. De-allocation using free

Failure to adhere to this pattern, such as using memory after a call to free (dangling pointer) or before a call to malloc (wild pointer), calling free twice («double free»), etc., usually causes a segmentation fault and results in a crash of the program.

These errors can be transient and hard to debug – for example, freed memory is usually not immediately reclaimed by the OS, and thus dangling pointers may persist for a while and appear to work.

On systems where it works, Valgrind is an invaluable tool for identifying what memory is leaked and where it was originally allocated.

Creating pointers to stack variables

Creating a pointer does not extend the life of the variable being pointed to. For example:

int* myFunction() 
{
    int x = 10;
    return &x;
}

Here, x has automatic storage duration (commonly known as stack allocation). Because it is allocated on the stack, its lifetime is only as long as myFunction is executing; after myFunction has exited, the variable x is destroyed. This function gets the address of x (using &x), and returns it to the caller, leaving the caller with a pointer to a non-existent variable. Attempting to access this variable will then invoke undefined behavior.

Most compilers don’t actually clear a stack frame after the function exits, thus dereferencing the returned pointer often gives you the expected data. When another function is called however, the memory being pointed to may be overwritten, and it appears that the data being pointed to has been corrupted.

To resolve this, either malloc the storage for the variable to be returned, and return a pointer to the newly created storage, or require that a valid pointer is passed in to the function instead of returning one, for example:

#include <stdlib.h>
#include <stdio.h>

int *solution1(void) 
{
    int *x = malloc(sizeof *x);
    if (x == NULL) 
    {
        /* Something went wrong */
        return NULL;
    }

    *x = 10;

    return x;
}

void solution2(int *x) 
{
    /* NB: calling this function with an invalid or null pointer 
       causes undefined behaviour. */

    *x = 10;
}

int main(void) 
{
    { 
        /* Use solution1() */

        int *foo = solution1();  
        if (foo == NULL)
        {
            /* Something went wrong */
            return 1;
        }

        printf("The value set by solution1() is %in", *foo);
        /* Will output: "The value set by solution1() is 10" */

        free(foo);    /* Tidy up */
    }

    {
        /* Use solution2() */

        int bar;
        solution2(&bar); 

        printf("The value set by solution2() is %in", bar);
        /* Will output: "The value set by solution2() is 10" */
    }

    return 0;
}

Incrementing / decrementing and dereferencing

If you write *p++ to increment what is pointed by p, you are wrong.

Post incrementing / decrementing is executed before dereferencing.
Therefore, this expression will increment the pointer p itself and return what was pointed by p before incrementing without changing it.

You should write (*p)++ to increment what is pointed by p.

This rule also applies to post decrementing: *p-- will decrement the pointer p itself, not what is pointed by p.

Dereferencing a Pointer

int a = 1;
int *a_pointer = &a;

To dereference a_pointer and change the value of a, we use the following operation

*a_pointer = 2;

This can be verified using the following print statements.

printf("%dn", a); /* Prints 2 */
printf("%dn", *a_pointer); /* Also prints 2 */

However, one would be mistaken to dereference a NULL or otherwise invalid pointer. This

int *p1, *p2;

p1 = (int *) 0xbad;
p2 = NULL;

*p1 = 42;
*p2 = *p1 + 1;

is usually undefined behavior. p1 may not be dereferenced because it points to an address 0xbad which may not be a valid address. Who knows what’s there? It might be operating system memory, or another program’s memory. The only time code like this is used, is in embedded development, which stores particular information at hard-coded addresses. p2 cannot be dereferenced because it is NULL, which is invalid.

Dereferencing a Pointer to a struct

Let’s say we have the following structure:

struct MY_STRUCT 
{
    int my_int;
    float my_float;
};

We can define MY_STRUCT to omit the struct keyword so we don’t have to type struct MY_STRUCT each time we use it. This, however, is optional.

typedef struct MY_STRUCT MY_STRUCT;

If we then have a pointer to an instance of this struct

MY_STRUCT *instance;

If this statement appears at file scope, instance will be initialized with a null pointer when the program starts. If this statement appears inside a function, its value is undefined. The variable must be initialized to point to a valid MY_STRUCT variable, or to dynamically allocated space, before it can be dereferenced. For example:

MY_STRUCT info = { 1, 3.141593F };
MY_STRUCT *instance = &info;

When the pointer is valid, we can dereference it to access its members using one of two different notations:

int a = (*instance).my_int;
float b = instance->my_float;

While both these methods work, it is better practice to use the arrow -> operator rather than the combination of parentheses, the dereference * operator and the dot . operator because it is easier to read and understand, especially with nested uses.

Another important difference is shown below:

MY_STRUCT copy = *instance;
copy.my_int    = 2;

In this case, copy contains a copy of the contents of instance. Changing my_int of copy will not change it in instance.

MY_STRUCT *ref = instance;
ref->my_int    = 2;

In this case, ref is a reference to instance. Changing my_int using the reference will change it in instance.

It is common practice to use pointers to structs as parameters in functions, rather than the structs themselves. Using the structs as function parameters could cause the stack to overflow if the struct is large. Using a pointer to a struct only uses enough stack space for the pointer, but can cause side effects if the function changes the struct which is passed into the function.

Function pointers

Pointers can also be used to point at functions.

Let’s take a basic function:

int my_function(int a, int b)
{
    return 2 * a + 3 * b;
}

Now, let’s define a pointer of that function’s type:

int (*my_pointer)(int, int);

To create one, just use this template:

return_type_of_func (*my_func_pointer)(type_arg1, type_arg2, ...)

We then must assign this pointer to the function:

my_pointer = &my_function;

This pointer can now be used to call the function:

/* Calling the pointed function */
int result = (*my_pointer)(4, 2);

...

/* Using the function pointer as an argument to another function */
void another_function(int (*another_pointer)(int, int))
{
    int a = 4;
    int b = 2;
    int result = (*another_pointer)(a, b);

    printf("%dn", result);
}

Although this syntax seems more natural and coherent with basic types, attributing and dereferencing function pointers don’t require the usage of & and * operators. So the following snippet is equally valid:

/* Attribution without the & operator */
my_pointer = my_function;

/* Dereferencing without the * operator */
int result = my_pointer(4, 2);

To increase the readability of function pointers, typedefs may be used.

typedef void (*Callback)(int a);

void some_function(Callback callback)
{
    int a = 4;
    callback(a);
}

Another readability trick is that the C standard allows one to simplify a function pointer in arguments like above (but not in variable declaration) to something that looks like a function prototype; thus the following can be equivalently used for function definitions and declarations:

void some_function(void callback(int))
{
    int a = 4;
    callback(a);
}

See also

Function Pointers

Initializing Pointers

Pointer initialization is a good way to avoid wild pointers. The initialization is simple and is no different from initialization of a variable.

#include <stddef.h>

int main()
{
    int *p1 = NULL; 
    char *p2 = NULL;
    float *p3 = NULL;

         /* NULL is a macro defined in stddef.h, stdio.h, stdlib.h, and string.h */

    ...
}    

In most operating systems, inadvertently using a pointer that has been initialized to NULL will often result in the program crashing immediately, making it easy to identify the cause of the problem. Using an uninitialized pointer can often cause hard-to-diagnose bugs.

Caution:

The result of dereferencing a NULL pointer is undefined, so it will not necessarily cause a crash even if that is the natural behaviour of the operating system the program is running on. Compiler optimizations may mask the crash, cause the crash to occur before or after the point in the source code at which the null pointer dereference occurred, or cause parts of the code that contains the null pointer dereference to be unexpectedly removed from the program. Debug builds will not usually exhibit these behaviours, but this is not guaranteed by the language standard. Other unexpected and/or undesirable behaviour is also allowed.

Because NULL never points to a variable, to allocated memory, or to a function, it is safe to use as a guard value.

Caution:

Usually NULL is defined as (void *)0. But this does not imply that the assigned memory address is 0x0. For more clarification refer to C-faq for NULL pointers

Note that you can also initialize pointers to contain values other than NULL.

int i1;

int main()
{
   int *p1 = &i1;
   const char *p2 = "A constant string to point to";
   float *p3 = malloc(10 * sizeof(float));
}

Address-of Operator ( & )

For any object (i.e, variable, array, union, struct, pointer or function) the unary address operator can be used to access the address of that object.

Suppose that

int i = 1;              
int *p = NULL;

So then a statement p = &i;, copies the address of the variable i to the pointer p.

It’s expressed as p points to i.

printf("%dn", *p); prints 1, which is the value of i.

Pointer Arithmetic

Please see here: Pointer Arithmetic

void* pointers as arguments and return values to standard functions

K&R

void* is a catch all type for pointers to object types. An example of this in use is with the malloc function, which is declared as

void* malloc(size_t);

The pointer-to-void return type means that it is possible to assign the return value from malloc to a pointer to any other type of object:

int* vector = malloc(10 * sizeof *vector);

It is generally considered good practice to not explicitly cast the values into and out of void pointers. In specific case of malloc() this is because with an explicit cast, the compiler may otherwise assume, but not warn about, an incorrect return type for malloc(), if you forget to include stdlib.h. It is also a case of using the correct behavior of void pointers to better conform to the DRY (don’t repeat yourself) principle; compare the above to the following, wherein the following code contains several needless additional places where a typo could cause issues:

int* vector = (int*)malloc(10 * sizeof int*);

Similarly, functions such as

void* memcpy(void *restrict target, void const *restrict source, size_t size);

have their arguments specified as void * because the address of any object, regardless of the type, can be passed in. Here also, a call should not use a cast

unsigned char buffer[sizeof(int)];
int b = 67;
memcpy(buffer, &b, sizeof buffer);

Const Pointers

Single Pointers

  • Pointer to an int

    The pointer can point to different integers and the int‘s can be changed through the pointer. This sample of code assigns b to point to int b then changes b‘s value to 100.

    int b;
    int* p;
    p = &b;    /* OK */
    *p = 100;  /* OK */
    
  • Pointer to a const int

    The pointer can point to different integers but the int‘s value can’t be changed through the pointer.

    int b;
    const int* p;
    p = &b;    /* OK */
    *p = 100;  /* Compiler Error */
    
  • const pointer to int

    The pointer can only point to one int but the int‘s value can be changed through the pointer.

    int a, b;
    int* const p = &b; /* OK as initialisation, no assignment */
    *p = 100;  /* OK */
    p = &a;    /* Compiler Error */
    
  • const pointer to const int

    The pointer can only point to one int and the int can not be changed through the pointer.

    int a, b;
    const int* const p = &b; /* OK as initialisation, no assignment */
    p = &a;   /* Compiler Error */
    *p = 100; /* Compiler Error */
    

Pointer to Pointer

  • Pointer to a pointer to an int

    This code assigns the address of p1 to the to double pointer p (which then points to int* p1 (which points to int)).

    Then changes p1 to point to int a. Then changes the value of a to be 100.

    void f1(void)
    {
      int a, b;
      int *p1;
      int **p;
      p1 = &b; /* OK */
      p = &p1; /* OK */
      *p = &a; /* OK */
      **p = 100; /* OK */
    }
    
  • Pointer to pointer to a const int

     void f2(void)
    {
      int b;
      const int *p1;
      const int **p;
      p = &p1; /* OK */
      *p = &b; /* OK */
      **p = 100; /* error: assignment of read-only location ‘**p’ */
    }
    
  • Pointer to const pointer to an int

    void f3(void)
    {
      int b;
      int *p1;
      int * const *p;
      p = &p1; /* OK */
      *p = &b; /* error: assignment of read-only location ‘*p’ */
      **p = 100; /* OK */
    }
    
  • const pointer to pointer to int

    void f4(void)
    {
      int b;
      int *p1;
      int ** const p = &p1; /* OK as initialisation, not assignment */
      p = &p1; /* error: assignment of read-only variable ‘p’ */
      *p = &b; /* OK */
      **p = 100; /* OK */
    }
    
  • Pointer to const pointer to const int

    void f5(void)
    {
      int b;
      const int *p1;
      const int * const *p;
      p = &p1; /* OK */
      *p = &b; /* error: assignment of read-only location ‘*p’ */
      **p = 100; /* error: assignment of read-only location ‘**p’ */
    }
    
  • const pointer to pointer to const int

    void f6(void)
    {
      int b;
      const int *p1;
      const int ** const p = &p1; /* OK as initialisation, not assignment */
      p = &p1; /* error: assignment of read-only variable ‘p’ */
      *p = &b; /* OK */
      **p = 100; /* error: assignment of read-only location ‘**p’ */
    }
    
  • const pointer to const pointer to int

    void f7(void)
    {
      int b;
      int *p1;
      int * const * const p = &p1; /* OK as initialisation, not assignment */
      p = &p1; /* error: assignment of read-only variable ‘p’  */
      *p = &b; /* error: assignment of read-only location ‘*p’ */
      **p = 100; /* OK */
    }
    

Same Asterisk, Different Meanings

Premise

The most confusing thing surrounding pointer syntax in C and C++ is that there are actually two different meanings that apply when the pointer symbol, the asterisk (*), is used with a variable.

Example

Firstly, you use * to declare a pointer variable.

int i = 5;
/* 'p' is a pointer to an integer, initialized as NULL */
int *p = NULL;
/* '&i' evaluates into address of 'i', which then assigned to 'p' */
p = &i;
/* 'p' is now holding the address of 'i' */

When you’re not declaring (or multiplying), * is used to dereference a pointer variable:

*p = 123;
/* 'p' was pointing to 'i', so this changes value of 'i' to 123 */

When you want an existing pointer variable to hold address of other variable, you don’t use *, but do it like this:

p = &another_variable;

A common confusion among C-programming newbies arises when they declare and initialize a pointer variable at the same time.

int *p = &i;

Since int i = 5; and int i; i = 5; give the same result, some of them might thought int *p = &i; and int *p; *p = &i; give the same result too. The fact is, no, int *p; *p = &i; will attempt to deference an uninitialized pointer which will result in UB. Never use * when you’re not declaring nor dereferencing a pointer.

Conclusion

The asterisk (*) has two distinct meanings within C in relation to pointers, depending on where it’s used. When used within a variable declaration, the value on the right hand side of the equals side should be a pointer value to an address in memory. When used with an already declared variable, the asterisk will dereference the pointer value, following it to the pointed-to place in memory, and allowing the value stored there to be assigned or retrieved.

Takeaway

It is important to mind your P’s and Q’s, so to speak, when dealing with pointers. Be mindful of when you’re using the asterisk, and what it means when you use it there. Overlooking this tiny detail could result in buggy and/or undefined behavior that you really don’t want to have to deal with.

Pointer to Pointer

In C, a pointer can refer to another pointer.

#include <stdio.h>
#include <stdlib.h>

int main(void) {
  int A = 42;
  int* pA = &A;
  int** ppA = &pA;
  int*** pppA = &ppA;

  printf("%d", ***pppA); /* prints 42 */

  return EXIT_SUCCESS;
}

But, reference-and-reference directly is not allowed.

#include <stdio.h>
#include <stdlib.h>

int main(void) {
  int A = 42;
  int* pA = &A;
  int** ppA = &&A; /* Compilation error here! */
  int*** pppA = &&&A;  /* Compilation error here! */

  ...

Introduction

A pointer is declared much like any other variable, except an asterisk (*) is placed between the type and the name of the variable to denote it is a pointer.

int *pointer; /* inside a function, pointer is uninitialized and doesn't point to any valid object yet */

To declare two pointer variables of the same type, in the same declaration, use the asterisk symbol before each identifier. For example,

int *iptr1, *iptr2;
int *iptr3,  iptr4;  /* iptr3 is a pointer variable, whereas iptr4 is misnamed and is an int */

The address-of or reference operator denoted by an ampersand (&) gives the address of a given variable which can be placed in a pointer of appropriate type.

int value = 1;
pointer = &value;

The indirection or dereference operator denoted by an asterisk (*) gets the contents of an object pointed to by a pointer.

printf("Value of pointed to integer: %dn", *pointer);
/* Value of pointed to integer: 1 */

If the pointer points to a structure or union type then you can dereference it and access its members directly using the -> operator:

SomeStruct *s = &someObject;
s->someMember = 5; /* Equivalent to (*s).someMember = 5 */

In C, a pointer is a distinct value type which can be reassigned and otherwise is treated as a variable in its own right. For example the following example prints the value of the pointer (variable) itself.

printf("Value of the pointer itself: %pn", (void *)pointer);
/* Value of the pointer itself: 0x7ffcd41b06e4 */
/* This address will be different each time the program is executed */

Because a pointer is a mutable variable, it is possible for it to not
point to a valid object, either by being set to null

pointer = 0;     /* or alternatively */
pointer = NULL;

or simply by containing an arbitrary bit pattern that isn’t a valid
address. The latter is a very bad situation, because it cannot be
tested before the pointer is being dereferenced, there is only a test
for the case a pointer is null:

if (!pointer) exit(EXIT_FAILURE);

A pointer may only be dereferenced if it points to a valid object,
otherwise the behavior is undefined.
Many modern implementations may help you by
raising some kind of error such as a segmentation fault and
terminate execution, but others may just leave your program in an
invalid state.

The value returned by the dereference operator is a mutable alias to the original variable, so it can be changed, modifying the original variable.

*pointer += 1;
printf("Value of pointed to variable after change: %dn", *pointer);
/* Value of pointed to variable after change: 2 */

Pointers are also re-assignable. This means that a pointer pointing to an object can later be used to point to another object of the same type.

int value2 = 10;
pointer = &value2;
printf("Value from pointer: %dn", *pointer);
/* Value from pointer: 10 */

Like any other variable, pointers have a specific type. You can’t
assign the address of a short int to a pointer to a long int, for
instance. Such behavior is referred to as type punning and is forbidden in C, though there are a few exceptions.

Although pointer must be of a specific type, the memory allocated for each type of pointer is equal to the memory used by the environment to store addresses, rather than the size of the type that is pointed to.

#include <stdio.h>

int main(void) {
    printf("Size of int pointer: %zun", sizeof (int*));      /* size 4 bytes */
    printf("Size of int variable: %zun", sizeof (int));      /* size 4 bytes */
    printf("Size of char pointer: %zun", sizeof (char*));    /* size 4 bytes */
    printf("Size of char variable: %zun", sizeof (char));    /* size 1 bytes */
    printf("Size of short pointer: %zun", sizeof (short*));  /* size 4 bytes */
    printf("Size of short variable: %zun", sizeof (short));  /* size 2 bytes */
    return 0;
}

(NB: if you are using Microsoft Visual Studio, which does not support the C99 or C11 standards, you must use %Iu1 instead of %zu in the above sample.)

Note that the results above can vary from environment to environment in numbers but all environments would show equal sizes for different types of pointer.

Extract based on information from Cardiff University C Pointers Introduction

Pointers and Arrays

Pointers and arrays are intimately connected in C. Arrays in C are always held in contiguous locations in memory. Pointer arithmetic is always scaled by the size of the item pointed to. So if we have an array of three doubles, and a pointer to the base, *ptr refers to the first double, *(ptr + 1) to the second, *(ptr + 2) to the third. A more convenient notation is to use array notation [].

double point[3] = {0.0, 1.0, 2.0};
double *ptr = point;

/* prints x 0.0, y 1.0 z 2.0 */ 
printf("x %f y %f z %fn", ptr[0], ptr[1], ptr[2]);

So essentially ptr and the array name are interchangeable. This rule also means that an array decays to a pointer when passed to a subroutine.

double point[3] = {0.0, 1.0, 2.0};

printf("length of point is %sn", length(point));

/* get the distance of a 3D point from the origin */ 
double length(double *pt)
{
   return sqrt(pt[0] * pt[0] + pt[1] * pt[1] + pt[2] * pt[2])
}

A pointer may point to any element in an array, or to the element beyond the last element. It is however an error to set a pointer to any other value, including the element before the array. (The reason is that on segmented architectures the address before the first element may cross a segment boundary, the compiler ensures that does not happen for the last element plus one).


Footnote 1: Microsoft format information can be found via printf() and format specification syntax.

Polymorphic behaviour with void pointers

The qsort() standard library function is a good example of how one can use void pointers to make a single function operate on a large variety of different types.

void qsort (
    void *base,                                 /* Array to be sorted */
    size_t num,                                 /* Number of elements in array */
    size_t size,                                /* Size in bytes of each element */
    int (*compar)(const void *, const void *)); /* Comparison function for two elements */

The array to be sorted is passed as a void pointer, so an array of any type of element can be operated on. The next two arguments tell qsort() how many elements it should expect in the array, and how large, in bytes, each element is.

The last argument is a function pointer to a comparison function which itself takes two void pointers. By making the caller provide this function, qsort() can effectively sort elements of any type.

Here’s an example of such a comparison function, for comparing floats. Note that any comparison function passed to qsort() needs to have this type signature. The way it is made polymorphic is by casting the void pointer arguments to pointers of the type of element we wish to compare.

int compare_floats(const void *a, const void *b)
{
    float fa = *((float *)a);
    float fb = *((float *)b);
    if (fa < fb)
        return -1;
    if (fa > fb)
        return 1;
    return 0;
}

Since we know that qsort will use this function to compare floats, we cast the void pointer arguments back to float pointers before dereferencing them.

Now, the usage of the polymorphic function qsort on an array «array» with length «len» is very simple:

qsort(array, len, sizeof(array[0]), compare_floats);

Понравилась статья? Поделить с друзьями:
  • Error begin was not declared in this scope
  • Error assigning to an array from an initializer list
  • Error assets api is not available мортал комбат мобайл
  • Error battery cannot charge
  • Error assert was not declared in this scope