Популярные услуги

Лекция 5

2021-03-09СтудИзба

Лекция  №5

                                                                       Целые (рассмотрели на прошлой лекции)

                                    арифметические       Вещественные (рассмотрели на прошлой лекции)

                                   символьные (рассмотрели на прошлой лекции)

базисные                   логические

типы                          порядковые (диапазонный)

данных                      ссылочный (указатели)

                                   процедурные (стоят немного особняком)             

Логический тип данных. На данный момент он есть почти во всех ЯП, и это самый простой тип данных. Он сводится всего к двум константам true и false, и имеет базовый набор операций (это операции алгебры логики): and, or, not. В С++ логический тип данных появился совершенно недавно. А в С, который был основой С++, логический тип данных отсутствует, хотя операции логические есть. Программисты на С прекрасно живут без типа bool, а программисты на С++ прекрасно жили в течение долгого времени. В С++ есть возможность перегрузки или перекрытия операций, а если тип BOOL является базовым типом данных, то для него такая возможность должна реализовываться. Т.е.

Рекомендуемые материалы

int f(int);

int f(bool);

должны быть разными функциями, и если для базисных типов это не разрешается, то это неправильный тип данных. Именно по этой причине в С++ появляется тип данных  BOOL, что упрощает переносимость с одной машины на другую. А как же быть с совместимостью программ на С и на С++ до введения типа BOOL? В стандарте было решено, что true имеет значение 1, а false – 0. И компилятор сам вставляет преобразование типа BOOL в любой целочисленный тип данных. Если у нас программа, где true – это любое ненулевое значение, то будет неявное преобразование его к 1. Все это сделано только для совместимости со старым кодом, написанном на С++. В других ЯП логический тип данных называется boolean. Там возможны преобразования из boolean в целый тип данных, но они должны быть явными. При этом true преобразуется в 1, false – в 0. Такие преобразования требуются редко. В первые такой тип появился в Алгло-60, хотя Фортран и развивался отдельно от Алгол, но в 64-м году там появляется тип данных logical. Не смотря на его внешнюю простоту иногда наличие данного типа данных значительно упрощает способ представления программ.

Порядковый или диапазонный тип.

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

                   0           1                       N-1

Type T is (val1, val2, … , valN);

Появляется  упорядоченность значений этого типа данных. Возникает естественный набор операций: succ – переход к следующему элементу, pred – взятие предыдущего элемента. Этот тип данных имел большой успех и после языка Паскаль перечислимый тип данных включается почти во все ЯП. Благодаря данному типу данных улучшается надежность и упрощается документация программ. Возникли проблемы, связанные с вводом-выводом. Во многих ЯП присутствует неявное преобразование в строковый вид не только при вводе, но и при выводе. Введена такая операция как (ord), допускается явное преобразование в целочисленные типы данных (int (T)), что по сути есть то же самое, что и (ord). Константы нумеруются от 0 до N-1, где N – мощность соответствующего типа. Когда мощность типа существенно меньше максимального целого числа перечислимый тип данных очень подходит для описания такого типа. Программа выглядит красивее, она становится более удобной для документации, более надежной, повышается читабельность программы.

Если есть преобразование из перечислимого типа данных в целый, то хотелось бы иметь преобразование из целого типа данных в перечислимый. В Модуле-2 есть псевдо-функция (псевдо потому что у нее аргумент переменного типа, это функция высшего типа, а таких функций в Модула-2 нет)

Val (Т e), где е – выражение целого типа, Т -  соответствующий перечислимый тип. Она возвращает выражение соответствующего перечислимого типа. Аналогичные функции есть и языке Ада, можем просто написать, используя общий синтаксис приведения ,

INTEGER(e) , где е – выражение соответствующего перечислимого типа. Вот тут-то мы и сталкиваемся с квазистатической концепцией контроля типа, которая для порядкового типа работает очень хорошо, а именно порядковый тип ограничивает изменчивость данных, которые на внутреннем уровне представляются как целочисленные, уменьшает их изменчивость и улучшает документируемость за счет присваивания  неявных имен. Так наш диапазон становится существенно меньше то возникает концепция квазистатического контроля, контроля, которая очень важна. Если преобразование (не важно, явное или неявное) является потенциально небезопасным, потому что может выводить за пределы диапазона, то преобразование а целый тип является безопасным, так как границы перечислимого типа обязательно меньше чем max_int, а вот обратное преобразование

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

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

‘A’ ß литерал перечисления.

Любая переменная, представимая как символьная (не строковая) переменная, может быть литералом перечисления, от сюда мы говорим не просто о константах перечисления, а о литералах перечисления. От сюда символический тип трактуется как частный тип перечислимого типа.  Это оказалось очень удачным решением, так как мы не привязываемся к конкретной кодировке (не важно как у нас кодируется, например, символ ‘A’), и у нас есть преобразование, внутренняя кодировка-то есть (внутренняя кодировка такая, которую задает перечислимый тип данных), перечислимого типа данных в целый и обратно (т.е. связь с целым типом данных прослеживается). Следовательно символьные типы совершенно не зависимы от внешнего представления кодировки. Символьные значения упорядочены, имеются эффективные операции перехода от одного символьного значения к другому.   Если нужно несколько наборов символов, то мы соответственно вводим char-set 1, char-set 2,  и работаем с соответствующими char-set. Но некоторые литералы, например цифры или буквы латинские, они явным образом будут пересекаться. И вот тут язык Ада отступает от идеологии остальных ЯП, где идентификаторы перечислимых типов были именами равноправными с другими, следовательно соответствующие константы должны были быть уникальными. Ели принимать такую трактовку, то надо допустить, чтобы литералы перечислений в разных перечислимых типах  пересекались. Например, можно ввести

1. Цвета экрана                                                                                                       type Screen Color is (…, Red , Green, Blue, White, Black)

2. Цвети типа кожи человека                                                                                type Skin Color is (White, Black, Yellow)

Теперь White (1) и White (2) различны. Если на Паскале нам надо было писать что-то типа  Skin_white, то в Аде этого не требуется. Мы говорили, что перекрытие имен возможно только для операций и невозможно для имен констант и переменных. Вспомним дискретную математику, где ноль и единица трактовались как соответствующие нульместные функции, возвращающие значение константы 0 или 1. Здесь литералы тоже можно трактовать как нульместные функции, возвращающие значения соответствующего литерала. Поэтому перекрытие имен литералов есть перекрытие имен функций, и за рамки концепций языка выхода нет. С точки зрения языка С++ такое невозможно, потому что нульместные функции в С++ перекрывать нельзя (в прототип функции при перекрытии входят только типы параметров). В Аде это возможно, потому что есть понятие контекста перекрытия и фиксирован не только тип параметров, но  и тип возвращаемого значения. В Аде понятие функции и процедуры не перемешано (функция обязательно имеет побочный эффект). Для вызова функции это есть контекст выражения, есть

   V:=e, то, так как неявного преобразования типа не допускается, тип правой и левой частей должны совпадать. Тогда компилятор, видя контекст и само выражение, может догадаться какую именно функцию ему надо сюда подставить. Если компилятор видит нечто

А := White , тип А известен, то компилятор знает, какой именно White сюда вставить. В случае, когда невозможно сразу определить тип выражения, то вставляется конструкция Т'е, которая является не приведением типа, а просто подсказкой компилятору. Это статически атрибутная функция.

Например в цикле

 For i in Black .. Yellow loop …

Не понятно, что такое Black .. Yellow, i – переменная, неявно описанная в теле цикла.  Компилятор в этом случае не будет угадывать, к чему относятся Black .. Yellow к Screen Color или к Skin Color, он просто скажет, что не может определить тип выражения и вот тут надо уточнять

 For i in Skin Color’Black .. Yellow loop …

Теперь понятно, что и i, и Yellow есть константы типа Skin Color, так как это и есть тот самый контекст. Это есть некоторое  усложнение языка не являющиеся необходимым, но очень полезное. Вспомним, что язык Ада проектировался по принципу сундука. В смысле перечислений язык Ада стоит некоторым особняком в отличие от остальных ЯП.

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

Enum Color { Red Green Blue}

Везде в программе, где появляются данные типа еnum Color они трактуются как данные целого типа. При этом никакого контроля нет.

Int i= Red;  корректно

Enum Color c = i+5; Мы можем переменной типа еnum Color присвоить произвольное целое значение. Одно полезное свойство в С от еnum есть, это облегчается способ документирования программ. Страуструпу, когда он должен был обеспечивать преемственность старых программ на С, никак не мог отказаться от еnum, но коль уж это некоторый тип данных, то ему надо было обеспечивать перекрытие. Страуструпу все-таки удалось это сделать за счет частичной потери совместимости со старыми программами на языке С. У компилятора есть возможность сделать неявное преобразование из типа данных enum в тип данных int. А вот неявного обратное преобразование уже не пройдет.  Такое преобразование может быть только явным. Точно так же, как есть неявное преобразование из типа   char в  int, а неявного обратного нет. 

Enum Color : byte {

…………………….

};

Делаем явное преобразование Color c = (color) (i + 5).  Такое преобразование от аналогичного преобразования в языке Ада отличается тем, что никакого квазистатического преобразования в языке С++ нет. Компилятор никогда не вставит за Вас в период выполнения никакие проверки : все проверки только в период компиляции. Вся ответственность за некорректные операции и возможности выхода за пределы диапазона полностью ложиться на программиста. Здесь проскакивает некоторая ненадежность языка С++, но все-таки enum – это есть базисный тип данных. Здесь, в отличие от других ЯП, мы можем давать произвольное значение соответствующим константам. В случае с цветами очень удобно дать представление RGB.

Enum Color RGB {

   Red = 0xFF0000

  Green = 0x00FF00

  Blue = 0x0000FF

};

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

Все хорошо и сточки зрения удобности документирования, и сточки зрения удобность применения. Но вот появляется язык Оберон, тот самый минимальный язык. Считается, что если без того что есть в языке Оберон, по крайней мере с точки зрения ООП, обойтись нельзя.  Перечислимого типа данных в языке Оберон нет. Получается ситуация на уровне Тараса Бульбы : «Я тебя породил, я тебя и убью».  Интересно, что включение перечислимого типа данных в языки программирования никем и никогда не оспаривалось, потому что, во-первых, перечислимый тип данных – это красиво ( никаких недостатков не было названо). Почему же Вирт исключил перечислимый тип данных из языка Оберон? Причин было две. Во-первых, с точки зрения  модульной структуры возникали некоторые проблемы с неявным экспортом имен.

Есть модуль М1 и М

М1               М                     Т


 


Он экспортирует тип данных Т и, следовательно, неявно за собой тянет целую кучу имен констант.  Это усложняет компиляцию. В языках Оберон, Модула-2, Delphi нет неявного импорта. Если Вы импортируете все имена из модуля М, а он в свою очередь импортирует все имена из модуля М1, это не значит, что у Вас включаются и все имена из модуля М1. А вторая причина, она же и основная, это то, что основная концепция Оберона – это расширение типа, то есть наследование, выведение одного типа из другого с добавлением в него новых свойств и операций. Перечислимый тип данных с самого начала противоречит концепции расширения, потому что при задании он слишком строго задает диапазон изменения.

Представим себе тип данных Т и функцию f, которая делает f(T) => enum, которая возвращает значение какого-то перечислимого типа данных enum, мы выводим тип данных Т1, для которого переопределяем функцию f(T1) => enum, которая тоже возвращает enum, таким образам расширяется диапазон входных значений, функциональность функции возрастает, а возвращаемый тип никак не меняется. Полезность понятия наследования становится несколько ограниченной. Если проанализировать наследование классов в языках, где есть перечислимый тип данных, то можно заметить тенденцию отказа от перечислимого типа данных в пользу целочисленного с явным перечислением соответствующего набора констант. Никто еще не придумал способ надежного и эффективного расширения перечислимого типа данных. Таким образом в основную концепцию языка Оберон перечислимый тип не вписывается , и Вирт от него отказался.

В Delphi перечислимый тип данных есть – он остался от языка Паскаль. В С++ перечислимый тип данных есть – он остался от языка С, к тому же С++ нам не навязывает явной необходимости ООП, как впрочем и Delphi.

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

static final int Red = 0xFF0000.

Посмотрим язык С# это 1999 год. Там перечислимый тип данных опять появляется. Зачем, если это противоречит концепции ООП? В описании языка С# авторы особо подчеркнули, что Главная причина – это то, что перечислимый тип данных хорошо сочетается и интегрированными средами программирования. Современные ЯП проектируются с учетом некоторой средой программирования (не обязательно интегрируемой).

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

Табличка:

Имя свойства Значения                                                             

                                                        


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

Пример.

Выравнивание текста


No aline

Left

Right

Center

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

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

IDL для описания прототипов, интерфейсов взаимодействия компонент. В ней есть перечислимые типы данных.

Если для С++ зафиксировано, что внутреннее представление перечислимых типов данных – это всегда int, то в С# есть некая оптимизация представления: мы можем указывать базисный тип данных (любой из целочисленных), который будет служить для внутреннего хранения.

Enum Color: byte {

……………………..

};

Можно предавать конкретные значения. Можно делать преобразования из перечислимого типа в базовый (если базовый не указан явно, то по умолчанию – это int). Все преобразования из базового типа в перечислимый только явные. Это касается всех значений за исключением константы 0.

Можно писать (Color != 0), и тут уже никаких преобразований уже не требуется.

Есть еще один интересный наворот.  Это уже некоторые «дебри» языка. Атрибуты в С# -- это совсем не то, что в других ЯП (вообще, программисты фирмы Micrifoft любят давать свои трактовки понятиям, которые уже определены). То, что они называют атрибутами, было ьы удобнее назвать прагматами (это слово появилось давно). Прагматы – это средства, которые служат для управления компилятором и средой времени выполнения.

Структура на С

Char c;

Int i;

с


i                                     Зазор, если машинное слово 4 байта, а chаr – 1.

           

Можно написать #pragma … , где опишет, что данную структуру не надо выравнивать, т.е. паковать. Набор прагматов колеблется от реализации к реализации.

Если у нас есть набор компонент, то лучше задать его как перечислимый тип данных. Это хорошо с точки зрения компонентного программирования. Сама по себе компонента является замкнутым классом. Мы используем компоненты в том виде, в котором они разработаны, ничего не планируя из них наследовать , и поэтому тут перечисления вполне могут играть свою роль.  Как мы уже говорили, они очень хорошо вкладываются в концепцию интегрированной визуальной среды разработки. Так вот с этой точки зрения перечислимый тип представляет из себя не последовательность значений, а непрерывный диапазон целых чисел, плюс каждому элементу мы предаем уникальный идентификатор для удобства документированности программ. Но довольно часто перечислимый тип может представляться не диапазоном, а разрознено, например, права доступа к файлу удобно делать с помощью классов.

Права доступа на чтение    READ 0x1

Права доступа на запись    WRITE 0x2

Права на чтение и запись будет объединение с помощью операции OR

READ|WRITE 0x3

Здесь некоторые противоречия с концепцией перечислимого типа. Перечислимый тип хорош тем, что  мы даем каждому значению свое уникальное имя. В данном случае хотелось бы несколько расширить набор операций, а именно допустить побитовые операции OR и AND. В С++ эта проблема решается с помощью неявного преобразования в целый тип и применения уже к нему логических операций. Язык С# поступает по-другому: там есть специальный атрибут, а мы говорили, что атрибут это есть фактически прагмат, т.е. способ управления интегрированной средой или  компилятором. Прагмат обычно записывается в квадратные скобки, т.е. [текст]. Есть специальный атрибут [Flags], который может стоять перед значениями перечислимого типа

Enum File access Rights {

WRITE = 2

READ = 1

READ_WRITE = READ or WRITE

};

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

Еще один порядковый тип данных, а именно ДИАПАЗОНЫ.

Диапазоны тоже впервые появились именно в языке Паскаль.

Type T = 0..N; где тип Т являлся контролируемым, т.е.  был квазистатический контроль. Диапазонным может быть только целый и перечислимый типы данных, если таковой имеется в ЯП. Базисный тип всегда является целым.

Х: Т;

Х:= е  (здесь будет вставлена проверка). 

Перечислимый тип наследуется от целочисленного типа данных. Если проверку, вставляемую компилятором, можно сразу выполнить, то она выполняется, если нет – то она остается на выполнение. То же самое, что было при переводе из перечислимого тапа данных в целочисленный (классический квазистатический контроль). После Паскаля диапазоны с удовольствием стали вставлять в другие ЯП. Но тенденция современных ЯП диапазоны не использовать. Вирт придумал и перечислимый тип данных, и диапазоны, но в Оберон он не включил ни тот , ни другой тип данных. Почему? Диапазон не расширяем, у него слишком жесткие ограничения. Диапазонов нет ни в JAVA, ни в C#, ни в других ЯП, которые появились в 90-е годы. Это еще и потому, что если нужен контроль ограничения, его можно описать на программном уровне с помощью класса.

Интересен подход к диапазонам языка Ада. Диапазоны всегда имеют базовый класс. В языке Паскаль базовый Тип никогда не указывался, потому что там по имени константы всегда можно было безошибочно определить, к какому типу данных относится данный тип данных. Ели определьть неля, то уже надо явно указывать. В Модула-2 был изменен немного синтаксис. Появился новый без знаковый тип данных CARDINAL. И появилась путаница с именами констант.

Если писать T=CARDINAL [0..N], то N обозначала, что базовый тип – без знаковый, а если T=[0..N] – то знаковый.

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

Type POSITIVE is new INTEGER range 1.. MAXINT;

Мы показали, что базовый тип INTEGER, также указали диапазон. Данные типа INTEGER и типа POSITEVE в одном  выражение смешивать нельзя.

INTEGER  POSITIVE

        i        :=    p;           нельзя,  компилятор не пропустит.

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

i := INTEGER (p);

p := POSITIVVE (i);

Данное  преобразование не будет квазистатическим, просто компилятор примет во внимание, что Вы знаете о том, что это разные типы данных, но по каким-то причинам хотите привести один к другому.

Мы можем обеспечить и другую семантику (Паскалевскую). С помощью подтипа.

Subtype CARDINAL is INTEGER range 0..MAXINT.

c := i; 

i := c;

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

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

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

Указатель – языковая абстракция понятия адреса. С точки зрения указателей все ЯП делятся на те, в которых есть понятие указателя, а на те, в которых данное понятие отсутствует. Из тех ЯП, которые мы рассматриваем, указатели полностью отсутствуют в языке JAVA, и они отсутствуют в C# (указатели присутствуют в неуправляемой части кода, но мы ее рассматривать не будем). Понятие указателя заменено на понятие ссылка. Во всех остальных ЯП, которые мы сейчас рассматриваем понятие указателя есть. Но опять же есть строгие и нестрогие языки.

Строгие языки, их представителями являются Паскаль, Оберон, Модула-2 и Ада. Там понятие указателя используется только для объектов из динамической памяти.

В Паскале инициализируем New(p),

А в Аде

type PT in access T;

X : PT;

X := new T;

Это говорит о том, что у нас в динамической памяти будет расположен объект типа Т, и на него будет ссылаться указатель Х.  Не поощряется смешивание указателей, полученных с помощью операции new, и обычных адресов памяти. Кроме как через указатели на объекты динамической памяти мы сослаться больше никак не можем. Здесь мы можем инициализовать указатель, присвоить один указатель другому, разыменовать указатель. В Обероне указатель служит для моделирования динамических объектов данных. По этому явной операции разыменования не требуется. Мы можем просто писать p.i, где i – соответствующее поле записи. 

Интересно, что если в языке Паскаль была процедура dispose, которая явно освобождала динамическую память, то нив Оберон, ни в Аде таких операций нет. В описании языка Оберон указывается, что должна быть обеспечена автоматическая сборка мусора. В Але по этому поводу ничего не сказано, но разработчиков компиляторов с языка Ада явно направляют на реализацию динамического сбора мусора. Динамическая сборка мусора, во-первых, связана с накладными расходами, а во- вторых, слабо контролируется программистом.   Динамическая сборка мусора может начать работу в тот момент, когда программист совершенно этого не ожидает. Если в системе важно максимальное время отклика, т.е. оно ограничено сверху, то такая концепция просто не применима. Поэтому строго зафиксировать автоматическую сборку мусора разработчики Ада не могли. Если она есть, то хорошо, а если нет, то в стандартной библиотеке предусмотрен пакет, в котором есть функция

UNCHECKED_DEALLOCATION (p); -- аналог освобождения динамической памяти.

Нестрогие языки – это все производные от языка С. 

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

1. мусор

2. «висячие» ссылки

1. Мусор.    

 Есть два указателя р1 и р2 типа Т.

р1            р1 := new T;         


р2              р2 := new T;


p1                      p2                        p1 := p2;


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

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

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

2. «Висячие» ссылки.

  Это обращение к памяти, которая уже освобождена.

Есть р1 и р1 – указатели.

р1        р2                  Дальше делаем UNCHECKED_DEALLOCATION (p1);

 


р1 = NULL;

И получаем:

р1        р2


И получается, что у нас  р2 – «висячая» ссылка. Хорошо, если такого рода операции написаны в программе рядом, а то бывает, что в самом начале сделаем присваивание, потом удалим и забудем, что делали присваивание и удаление. Позже напишем что-то типа р.i = ….., где i поле записи. 

(В Аде синтаксис разыменования немного похож на Обероновский. Если пишем p.i, то ссылаемся на конкретное поле записи, а если хотим сослаться на всю запись целиком, например, передать в качестве параметра функции, то пишем p.all.)

Что произойдет в этот момент? Это зависит от менеджера динамической памяти. Самое противное с точки зрения программиста то, что именно в данный момент ничего не произойдет, но в некоторые моменты программа будет давать сбой, причем вычислить внешние условия, при которых это случается, будет практически невозможно. Или при переносе с одной системы на другую это обнаружится. В одной системе программа работала, а в другой – нет.

while (p != NULL) {

  free (p);

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

p = p -> next;

В лекции "19. Заболевания, передаваемые половым путём" также много полезной информации.

}

 

Будет ли работать такой фрагмент программы?  Это типичная ошибка. Мы сначала освобождаем р, а потом ссылаемся на освобожденный участок памяти. Если написать простое консольное приложение на Visual C++, то программа прекрасно будет работать, потому что она однопоточная. А если программа многопоточная, менеджер одновременно работает сразу со всеми потоками (потоки отличаются от процессов тем, что все они работают в одном адресном пространстве). Ваш поток прерывается, другой поток запрашивает память, менеджер динамической памяти отдает ему именно этот кусок, и Вы его дальше начинаете портить. Что будет дальше, кто и в какой момент слетит, предугадать очень сложно.  Это первая проблема.

Если перенести эту программу на архитектуру фирмы SUN, то там, как только освобождается страница виртуальной памяти, менеджер сразу отдает ее операционной системе. И тогда здесь уже будет ошибка адресации. Наш указатель р с большой вероятностью будет ссылаться на адресное пространство, которое отдано операционной системе. Следовательно на данной архитектуре программа сорвется.

На одной системе программа работает, на другой – нет, а самое интересное, когда мы найдем соответствующую ошибку?!

Системы с автоматической сборкой мусора эти проблемы решают.

Свежие статьи
Популярно сейчас
А знаете ли Вы, что из года в год задания практически не меняются? Математика, преподаваемая в учебных заведениях, никак не менялась минимум 30 лет. Найдите нужный учебный материал на СтудИзбе!
Ответы на популярные вопросы
Да! Наши авторы собирают и выкладывают те работы, которые сдаются в Вашем учебном заведении ежегодно и уже проверены преподавателями.
Да! У нас любой человек может выложить любую учебную работу и зарабатывать на её продажах! Но каждый учебный материал публикуется только после тщательной проверки администрацией.
Вернём деньги! А если быть более точными, то автору даётся немного времени на исправление, а если не исправит или выйдет время, то вернём деньги в полном объёме!
Да! На равне с готовыми студенческими работами у нас продаются услуги. Цены на услуги видны сразу, то есть Вам нужно только указать параметры и сразу можно оплачивать.
Отзывы студентов
Ставлю 10/10
Все нравится, очень удобный сайт, помогает в учебе. Кроме этого, можно заработать самому, выставляя готовые учебные материалы на продажу здесь. Рейтинги и отзывы на преподавателей очень помогают сориентироваться в начале нового семестра. Спасибо за такую функцию. Ставлю максимальную оценку.
Лучшая платформа для успешной сдачи сессии
Познакомился со СтудИзбой благодаря своему другу, очень нравится интерфейс, количество доступных файлов, цена, в общем, все прекрасно. Даже сам продаю какие-то свои работы.
Студизба ван лав ❤
Очень офигенный сайт для студентов. Много полезных учебных материалов. Пользуюсь студизбой с октября 2021 года. Серьёзных нареканий нет. Хотелось бы, что бы ввели подписочную модель и сделали материалы дешевле 300 рублей в рамках подписки бесплатными.
Отличный сайт
Лично меня всё устраивает - и покупка, и продажа; и цены, и возможность предпросмотра куска файла, и обилие бесплатных файлов (в подборках по авторам, читай, ВУЗам и факультетам). Есть определённые баги, но всё решаемо, да и администраторы реагируют в течение суток.
Маленький отзыв о большом помощнике!
Студизба спасает в те моменты, когда сроки горят, а работ накопилось достаточно. Довольно удобный сайт с простой навигацией и огромным количеством материалов.
Студ. Изба как крупнейший сборник работ для студентов
Тут дофига бывает всего полезного. Печально, что бывают предметы по которым даже одного бесплатного решения нет, но это скорее вопрос к студентам. В остальном всё здорово.
Спасательный островок
Если уже не успеваешь разобраться или застрял на каком-то задание поможет тебе быстро и недорого решить твою проблему.
Всё и так отлично
Всё очень удобно. Особенно круто, что есть система бонусов и можно выводить остатки денег. Очень много качественных бесплатных файлов.
Отзыв о системе "Студизба"
Отличная платформа для распространения работ, востребованных студентами. Хорошо налаженная и качественная работа сайта, огромная база заданий и аудитория.
Отличный помощник
Отличный сайт с кучей полезных файлов, позволяющий найти много методичек / учебников / отзывов о вузах и преподователях.
Отлично помогает студентам в любой момент для решения трудных и незамедлительных задач
Хотелось бы больше конкретной информации о преподавателях. А так в принципе хороший сайт, всегда им пользуюсь и ни разу не было желания прекратить. Хороший сайт для помощи студентам, удобный и приятный интерфейс. Из недостатков можно выделить только отсутствия небольшого количества файлов.
Спасибо за шикарный сайт
Великолепный сайт на котором студент за не большие деньги может найти помощь с дз, проектами курсовыми, лабораторными, а также узнать отзывы на преподавателей и бесплатно скачать пособия.
Популярные преподаватели
Добавляйте материалы
и зарабатывайте!
Продажи идут автоматически
5173
Авторов
на СтудИзбе
436
Средний доход
с одного платного файла
Обучение Подробнее