July 5, 2016

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

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


Указатели


POINTER TO — это указатель на переменную. Может указывать как на данные, так и на другие указатели, а также на функциональные блоки, программы, методы и функции. Не могут указывать на ссылки.
Минутка занудства: можно сделать так — POINTER TO POINTER TO POINTER TO..., но особого смысла это не имеет, а вот указатель-на-указатель иногда применяется. Например, когда необходимо переключаться между несколькими переменными или функциями.

К указателям применима арифметика: к адресу указателя можно прибавить число и наш указатель будет указывать на другую ячейку памяти, т. е. на совсем другие данные. Для этого указатели и нужны. В этом месте обычно напоминают про "отстрелить себе ногу", т. к. очень легко наприбавлять столько, что указатель будет указывать в неизвестном направлении.

Массивы — это тоже указатели, только завуалированные; и это была первая попытка человечества перестать отстреливать себе конечности, так как для массивов есть проверка на выход за границы массива.
Указатель на массив повторно разрешает членовредительство.

Чтобы подобраться ближе к данным, на которые указывает указатель, используют операцию ^  -разыменование указателя. Указатель от этого не портится и не разыменовывается, мы просто записываем данные в ту ячейку, на которую указывает указатель.

PROGRAM MAIN
VAR
    pt_a    : POINTER TO INT;
    pt_pt_a : POINTER TO POINTER TO INT;
    fbTest  : FB_Test;
    pt_fb   : POINTER TO FB_Test;

// [...]

a       := 123;
pt_a    := ADR(a);             // pt_a указывает на 'a'
pt_pt_a := ADR(pt_a);          // pt_pt_a указывает на pt_a который указывает на 'a'
pt_a^   := 67;                 // 'a' теперь равно 67
pt_fb   := ADR(fbTest);

fbTest(inp := a, in_out := a);
pt_fb^(inp := a, in_out := a); // разыменование указателя на ФБ и запуск ФБ


Ссылки


REFERENCE TO — ссылаться можно только на данные (и структуры, они тоже данные) и указатели (получим второе имя для указателя). Ссылки с арифметикой не дружат, поэтому они надежнее: выйти за границы приличия не получится.

Можно считать, что ссылка это та же самая переменная только с другим именем: ячейка памяти одна — имени два (или три, или четыре, сколько пожелаете).

PROGRAM MAIN
VAR
    a         : INT;
    ref_a     : REFERENCE TO INT;
    ref_pt_a  : REFERENCE TO POINTER TO INT;
    pt_ref_a  : POINTER TO INT;

// [...]

a         :=    123;
ref_a     REF=  a;
pt_ref_a  :=    ADR(ref_a); // указатель на ссылку
ref_a     :=    67;         // 'a' теперь равно 67
ref_a     REF=  0;          // ссылка больше ни на что не указывает
pt_ref_a^ :=    44;         // 'a' теперь равно 44


Для получения ссылки используется оператор REF=. Слитно три буквы и знак равно, все без пробелов. Это и есть оператор получения ссылки. На картинке ниже видно как переменная и ссылка на нее принимают одинаковое значение = 67:



Стоит обратить внимание на указатель pt_ref_a, который хоть и пытается указывать на ссылку ref_a, но мы то уже знаем, что ref_a это всего-лишь второе название для переменной 'a'. Поэтому в итоге указатель указывает на переменную 'a' типа INT.



После присваивания ссылке нуля (в предпоследней строке), указатель pt_ref_a по прежнему указывает на переменную 'a', хотя ссылка ref_a уже никуда не ссылается. Это подтверждает что мы просто удаляем "второе" название переменной 'а', в то время как сама переменная остается на месте (соответственно, сохраняется и указатель на нее).


Зачем ссылки?


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

Давайте воспользуемся ссылкой, ведь для нее арифметика не работает! Все правильно, и за нас это уже сделали.

Любой ФБ, кроме входных параметров VAR_INPUT и VAR_OUTPUT имеет набор VAR_IN_OUT. Если первые два принимают входные данные по значению, то последний IN_OUT принимает входной параметр как ссылку. Изменяя ее значение внутри ФБ, мы сможем изменять значение переменной снаружи ФБ, ведь ссылка это второе имя переменной.

FUNCTION_BLOCK FB_Test
VAR_INPUT
    inp    : INT;

VAR_OUTPUT
    out    : INT;

VAR_IN_OUT
    in_out : INT;

// [...]

inp    := 44;
out    := 55;
in_out    := 77;


Запускаем и смотрим в отладчике на результат:



ФБ изнутри изменил значение внешней переменной 'a', сменив ее значение на = 77. Изменение входного параметра inp внутри ФБ на = 44, никак не повлияло на значение переменной 'a', так как он принимает параметр по значению (просто создает локальную копию значения).


Массивы переменной длины


Мы разрабатываем ФБ, который как-то там должен изменять содержимое массива (например, заполнять латинскими лорем-ипсумами или нулями). Внимание, вопрос — как передавать в ФБ массивы разной длины?

FUNCTION_BLOCK POUPIUPOW
VAR_IN_OUT
    VarArray : ARRAY [*] OF INT;
END_VAR
VAR
    start    : DINT;
    end      : DINT;
END_VAR

// [...]

start := LOWER_BOUND(VarArray, 1);
end   := UPPER_BOUND(VarArray, 1);


Работает только для VAR_IN_OUT параметров (иначе как бы мы влияли на внешние к ФБ массивы).
Совместимо с третьей редакцией стандарта МЭК.
Для определения габаритов или просто начала и конца массива используются функции LOWER_BOUND и UPPER_BOUND. Второй параметр этих функций — это номер размерности: 1 — получить длину одномерного массива; 1..2 — длину строки или кол-во столбцов для двумерного массива; 1..3 — длина строки, столбца или глубины для 3D массива.

No comments

Post a Comment

Note: Only a member of this blog may post a comment.