Зачем нужны ссылки в c: Что не так с ссылками в С++ / Хабр

Что такое указатели в программировании — Журнал «Код» программирование без снобизма

В программировании есть понятие указателей — особенно часто о них можно услышать в языках вроде C. Указатели считаются сложной темой, и про тех, кто ими пользуются, ходят легенды. Но на самом деле в указателях нет ничего сложного. Сейчас разберём. 

Что такое указатель

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

Количество этих ячеек зависит от типа данных, который хранится в этой переменной: обычно для целого числа выделяют 2 или 4 байта, для дробного — 8 байт, для строки — столько же, сколько и символов и ещё 1 служебный байт и так далее. Но сколько бы байтов ни выделил компьютер для хранения, он выделяет эти байты подряд, друг за другом, и запоминает два момента:

  1. Сколько байтов занимает переменная.
  2. По какому адресу в памяти находится первый байт этой переменной.

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

Зачем это нужно

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

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

Что такое сборщик мусора в программировании

Почему указателями редко пользуются

Если в указатель положить адрес памяти, который выходит за границы, выделенные для этой программы, то мы можем повредить чужие данные. Некоторые языки, например, C++, не всегда перепроверяют то, что делает программист, поэтому там легко сломать не только свою программу, но и весь компьютер.

Дело в том, что компьютеру всё равно, что лежит по адресу, написанному в указателе — данные, переменная или что-то чужое. Всё, что он делает — это идёт по этому адресу и стирает либо записывает туда что-то своё, не задавая лишних вопросов. Это мощная штука, но чтобы ей пользоваться, нужно чётко знать внутреннюю архитектуру памяти и работы компилятора.

В каких языках есть указатели

Самые популярные языки с поддержкой указателей — это всё семейство Си-языков:

А всё потому, что в старые времена оперативной памяти было мало и нужно было использовать её как можно эффективнее. Поэтому иногда было нормально, когда одна и та же область памяти использовалась программистом для хранения разных данных.

Также полноценные указатели есть в некоторых современных языках высокого уровня: Java, Pascal и Go.

Текст:

Михаил Полянин

Редактор:

Максим Ильяхов

Художник:

Алексей Сухов

Корректор:

Ирина Михеева

Вёрстка:

Кирилл Климентьев

Соцсети:

Виталий Вебер

C++ | Операции с указателями

Последнее обновление: 24. 02.2023

Указатели поддерживают ряд операций: присваивание, получение адреса указателя, получение значения по указателю,
некоторые арифметические операции и операции сравнения.

Присваивание адреса

Указателю можно присвоить адрес объекта того же типа, либо значение другого указателя. Для получения адреса объекта используется операция &:


int a {10};
int *pa {&a};	// указатель pa хранит адрес переменной a

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

Разыменование указателя

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


#include <iostream>

int main()
{
    int a {10};
    int *pa {&a};   // хранит адрес переменной a

    std::cout << "*pa = " << *pa << std::endl;  // *pa = 10
    std::cout << "a = " << a << std::endl;      // a = 10

    *pa = 25;   // меняем значение по адресу в указателе
      
    std::cout << "*pa = " << *pa << std::endl;  // *pa = 25
    std::cout << "a = " << a << std::endl;      // a = 25
}

Через выражение *pa мы можем получить значение по адресу, который хранится в указателе pa, а через выражение типа
*pa = значение вложить по этому адресу новое значение.

И так как в данном случае указатель pa указывает на переменную a, то при изменении значения по адресу, на который указывает указатель, также изменится и значение
переменной a.

Присвоение указателю другого указателя

Присвоение указателю другого указателя:


#include <iostream>

int main()
{
	int a {10};
    int b {2};
      
    int *pa {&a};   // указатель на переменную a
    int *pb {&b};   // указатель на переменную b
      
    std::cout << "pa: address=" << pa << "\t value=" << *pa << std::endl;
    std::cout << "pb: address=" << pb << "\t value=" << *pb << std::endl;
      
    pa = pb;    // теперь указатель pa хранит адрес переменной b
    std::cout << "pa: address=" << pa << "\t value=" << *pa << std::endl;
	*pa = 125;	// меняем значение по адресу в указателе pa
    std::cout << "b value=" << b << std::endl;
}

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


pa: address=0x56347ffc5c         value=10
pb: address=0x56347ffc58         value=2
pa: address=0x56347ffc58         value=2
b value=125

Нулевые указатели

Нулевой указатель (null pointer) — это указатель, который не указывает ни на какой объект. Если мы не хотим, чтобы указатель указывал на какой-то конкретный адрес,
то можно присвоить ему условное нулевое значение. Для определения нулевого указателя можно инициализировать указатель нулем или константой nullptr:


int *p1{nullptr};
int *p2{};

Ссылки на указатели

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


#include <iostream>

int main()
{
	int a {10};
	int b {6};
	
	int *p{};			// указатель
	int *&pRef {p};		// ссылка на указатель
	pRef = &a;			// через ссылку указателю p присваивается адрес переменной a
	std::cout << "p value=" << *p << std::endl;	// 10
	*pRef = 70;			// изменяем значение по адресу, на который указывает указатель
	std::cout << "a value=" << a << std::endl;	// 70
	
	pRef = &b;			// изменяем адрес, на который указывает указатель
	std::cout << "p value=" << *p << std::endl;	// 6
}

Адрес указателя

Указатель хранит адрес переменной, и по этому адресу мы можем получить значение этой переменной. Но кроме того, указатель, как и любая переменная, сам имеет адрес, по которому он располагается в памяти.
Этот адрес можно получить также через операцию &:


int a {10};
int *pa {&a};
std::cout << "address of pointer=" << &pa << std::endl;        // адрес указателя
std::cout << "address stored in pointer=" << pa << std::endl;  // адрес, который хранится в указателе - адрес переменной a         
std::cout << "value on pointer=" << *pa << std::endl;          // значение по адресу в указателе - значение переменной a

Операции сравнения

К указателям могут применяться операции сравнения >, >=,
<, <=,==, !=. Операции сравнения применяются только к
указателям одного типа. Для сравнения используются номера адресов:


#include <iostream>

int main()
{
	int a {10};
    int b {20};
    int *pa {&a};
    int *pb {&b};
	
	if(pa > pb)
		std::cout << "pa (" << pa << ") is greater than pb ("<< pb << ")" << std::endl;
	else
		std::cout << "pa (" << pa << ") is less or equal pb ("<< pb << ")" << std::endl;
}

Консольный вывод в моем случае:


pa (0xa9da5ffdac) is greater than pb (0xa9da5ffda8)

Приведение типов

Иногда требуется присвоить указателю одного типа значение указателя другого типа. В этом случае следует выполнить операцию приведения типов с помощью операции (тип_указателя *):


#include <iostream>

int main()
{
	char c {'N'};
	char *pc {&c};			  // указатель на символ
	int *pd {(int *)pc};	  // указатель на int
	void *pv {(void*)pc};	  // указатель на void
	std::cout << "pv=" << pv << std::endl;
	std::cout << "pd=" << pd << std::endl;
}

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

Кроме того, следует отметить, что указатель на тип char (char *pc {&c}) при выводе на консоль система интерпретирует как строку:

std::cout << "pc=" << pc << std::endl;

Поэтому если мы все-таки хотим вывести на консоль адрес, который хранится в указателе типа char, то это указатель надо преобразовать к другому типу, например, к void* или к int*.

НазадСодержаниеВперед

symlink — переход по символическим ссылкам в C

спросил

Изменено
10 лет, 7 месяцев назад

Просмотрено
16 тысяч раз

Я хочу написать программу на C, которая, учитывая имя символической ссылки, будет печатать имя файла или каталога, на который указывает ссылка. Любые предложения о том, как начать?

  • c
  • символическая ссылка

2

Упомянутая функция readlink() является частью ответа. Однако вы должны знать о его ужасном интерфейсе (он не завершает строку ответа нулем!).

Вы также можете посмотреть на функцию realpath() , использование которой обсуждалось в SO 1563186. Вы также можете посмотреть код для ‘linkpath’ в архиве программного обеспечения IIUG. Он анализирует безопасность всех обнаруженных каталогов при разрешении символической ссылки — он использует readlink() и lstat() и stat() ; одна из проверок при тестировании программы заключалась в том, чтобы убедиться, что realpath() разрешает имя в тот же файл.

0

Убедитесь, что ваша среда поддерживает функции POSIX, включите unistd.h , а затем используйте функцию readlink.

В зависимости от платформы, stat() или fstat() , вероятно, первое, что нужно попробовать. Если вы используете Linux или cygwin, то 9Программа 0021 stat даст вам разумное представление о том, чего ожидать от вызова системного API (она в значительной степени дает вам его текстовый дамп).

1

Вам нужен системный вызов readlink(). Берет путь к ссылке, возвращает строку (, а не , всегда допустимый путь в файловой системе!), хранящуюся в ссылке. Подробности смотрите на справочной странице («man 2 readlink»).

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

2

Зарегистрируйтесь или войдите в систему

Зарегистрируйтесь с помощью Google

Зарегистрироваться через Facebook

Зарегистрируйтесь, используя адрес электронной почты и пароль

Опубликовать как гость

Электронная почта

Требуется, но никогда не отображается

Опубликовать как гость

Электронная почта

Требуется, но не отображается

Объяснение жестких и программных ссылок в Linux

Опубликовано:
21 сентября 2020 г.

|

|

на
Тайлер Кэрриган (редакционная группа, Red Hat)

Изображение

Изображение

Dawid Śliwka с сайта Pixabay

Вы когда-нибудь были знакомы с чем-то, работали над этим, но не до конца понимали его принципы? Я чувствую, что это случается со мной чаще, чем с большинством людей. Это неприятное чувство, но оно часто легко устраняется. Иногда требуется только, чтобы кто-то объяснил концепцию на «простом английском языке», то есть терминах непрофессионала. Это цель этой статьи. Я хочу поговорить о жестких ссылках и мягкие (символические) ссылки в самых основных возможных терминах. Вы можете понять, что эта концепция, которая часто вызывает трудности у системных администраторов, довольно проста. По крайней мере, я расскажу вам о синтаксисе для создания этих ссылок (многим людям трудно его запомнить). Итак, давайте приступим к делу.

Жесткие ссылки

Понятие жесткой ссылки является самым основным, что мы обсудим сегодня. Каждый файл в файловой системе Linux начинается с одной жесткой ссылки. ссылка находится между именем файла и фактическими данными, хранящимися в файловой системе. Создание дополнительной жесткой ссылки на файл означает несколько разные вещи. Давайте обсудим это.

Сначала вы создаете новое имя файла, указывающее на точно такие же данные, что и старое имя файла. Это означает, что два имени файла, хотя и разные, указывают на идентичные данные. Например, если я создам файл /home/tcarrigan/demo/link_test и напишу в нем hello world , у меня будет одна жесткая ссылка между именем файла link_test и содержимое файла hello world .

 [tcarrigan@server demo]$ ls -l
всего 4
-рв-рв-р--. 1 tcarrigan tcarrigan 12, 29 августа, 14:27 link_test 

Обратите внимание на количество ссылок здесь ( 1 ).

Затем я создаю новую жесткую ссылку в /tmp на тот же самый файл, используя следующую команду:

 [tcarrigan@server demo]$ ln link_test /tmp/link_new 

путь) (новый путь к файлу) .

Теперь, когда я смотрю на свою файловую систему, я вижу обе жесткие ссылки.

 [tcarrigan@server demo]$ ls -l link_test /tmp/link_new
-рв-рв-р--. 2 ткарриган ткарриган 12 авг 29 14:27 link_test
-рв-рв-р--. 2 tcarrigan tcarrigan 12, 29 августа, 14:27 /tmp/link_new 

Основное отличие здесь заключается в имени файла. Количество ссылок также было изменено ( 2 ). В частности, если я cat содержимое нового файла, он отображает исходные данные.

 [tcarrigan@server demo]$ cat /tmp/link_new
привет мир 

Когда в одно имя файла вносятся изменения, другое отражает эти изменения. Разрешения, количество ссылок, право собственности, временные метки и содержимое файла точно такие же. Если исходный файл удален, данные по-прежнему существуют по вторичной жесткой ссылке. Данные удаляются с вашего диска только после удаления всех ссылок на данные. Если вы найдете два файла с одинаковыми свойствами, но не уверены, связаны ли они жестко, используйте команду ls -i для просмотра номера inode . Файлы, которые жестко связаны друг с другом, имеют один и тот же номер индекса.

 [tcarrigan@server demo]$ ls -li link_test /tmp/link_new
2730074 -rw-rw-r--. 2 ткарриган ткарриган 12 авг 29 14:27 link_test
2730074 -rw-rw-r--. 2 tcarrigan tcarrigan 12, 29 августа, 14:27 /tmp/link_new 

Общий индексный дескриптор — 2730074 , что означает, что данные в этих файлах идентичны.

Если вам нужна дополнительная информация об инодах, прочитайте мою полную статью здесь.

Жесткие ограничения

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

Мягкие ссылки

Обычно называемые символическими ссылками , мягкие ссылки соединяют нерегулярные и обычные файлы. Они также могут охватывать несколько файловых систем. По определению программная ссылка — это не стандартный файл, а специальный файл, который указывает на существующий файл. Давайте посмотрим, как создать мягкую ссылку. Я использую команду ln -s и следующий синтаксис:

ln -s (путь к файлу, на который вы хотите указать) (новый путь к файлу)

В приведенном ниже примере я создаю новый файл в /home/tcarrigan/demo/soft_link_test с содержимым файла soft Hello world . Затем я создаю программную ссылку на этот файл по адресу /tmp/soft_link_new :

 [tcarrigan@server demo]$ ln -s /home/tcarrigan/demo/soft_link_test /tmp/soft_link_new
[tcarrigan@server demo]$ ls -l soft_link_test /tmp/soft_link_new
-рв-рв-р--. 1 ткарриган ткарриган 17 авг 30 11:59 soft_link_test
lwxrwxrwx. 1 tcarrigan tcarrigan 35 30 августа 12:09 /tmp/soft_link_new -> /home/tcarrigan/demo/soft_link_test 

Обратите внимание, что /tmp/soft_link_new — это просто символическая ссылка, указывающая на исходный /home/tcarrigan/demo/soft_link_test . Если я cat содержимое /tmp/soft_link_new , я должен увидеть текст soft Hello world .

 [tcarrigan@server demo]$ cat /tmp/soft_link_new
soft Hello world 

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

Жесткий или мягкий?

Здесь нет однозначного ответа. Лучшая ссылка — это тип, который подходит для вашей конкретной ситуации. Хотя эти концепции могут быть сложными для запоминания, синтаксис довольно прост, так что это плюс! Чтобы вам было легко разделить эти два понятия, я оставлю вас с этим:

  • Жесткая ссылка всегда указывает имя файла на данные на устройстве хранения.

    This entry was posted in Популярное