Программирование >>  Перегруженные имена функций и идентификаторы 

1 ... 124 125 126 [ 127 ] 128 129 130 ... 210


inline B::~B() { }

inline B::B() : a1( a1 ) { }

void B::f() { }

class C : public B

public:

C();

protected: A a2;

inline C::C() : a2( a2 )

int main()

B* ptr = new C(); delete ptr; return 0;

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

A::~A() for a1

Немного не то, что ожидалось? Тогда поставим перед названием деструктора класса B слово virtual. Результат изменится:

A::~A() for a2 A::~A() for a1

Сейчас вывод программы несколько более соответствует действительности.

Запись структур данных в двоичные файлы

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



данные или, наоборот, восстановить состояние прошлой сессии работы с программой.

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

Итак, как все это выглядит обычно? Имеется некоторая структура данных:

struct data item

type 1 field 1; type 2 field 2; ...

type n field n;

data item i1;

Каким образом, например, сохранить информацию из i1 так, чтобы программа во время своего повторного запуска, смогла восстановить ее? Наиболее частое решение следующее:

FILE* f = fopen( file , wb ); fwrite((char*)&i1, sizeof(i1), 1, f); fclose(f);

assert расставляется по вкусу, проверка инвариантов в данном примере не является сутью. Тем не менее, несмотря на частоту использования, этот вариант решения проблемы не верен.

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

FILE* f = fopen( file , rb );

fread((char*)&i1, sizeof(i1), 1, f); fclose(f);

Что же тут неправильного? Ну что же, для этого придется немного пофилософствовать. Как бы много не говорили о том, что Си - это почти то же самое, что и ассемблер, не надо забывать, что он является все-таки языком высокого уровня. Следовательно, в принципе, программа написанная на Си (или C++) может (теоретически) компилироваться на разных



компиляторах и разных платформах. К чему это? К тому, что данные, которые сохранены подобным образом, в принципе не переносимы.

Стоит вспомнить о том, что для структур неизвестно их физическое представление. То есть, для конкретного компилятора оно, быть может, и известно (для этого достаточно посмотреть работу программы вооруженным взглядом , т.е. отладчиком), но о том, как будут расположены в памяти поля структуры на какой-нибудь оригинальной машине, неизвестно. Компилятор со спокойной душой может перетасовать поля (это, в принципе, возможно) или выровнять положение полей по размеру машинного слова (встречается сплошь и рядом). Для чего? Для увеличения скорости доступа к полям. Понятно, что если поле начинается с адреса, не кратного машинному слову, то прочитать его содержимое не так быстро, как в ином случае. Таким образом, сохранив данные из памяти в бинарный файл напрямую мы получаем дамп памяти конкретной архитектуры (не говоря о том, что sizeof совершенно не обязан возвращать количество байт).

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

Обычный костыль , который применяется, например, при проблемах с выравниванием, заключается в том, что компилятору явно указывается как надо расставлять поля в структурах. В принципе, любой компилятор дает возможность управлять выравниванием. Но выставить одно значение для всего проекта при помощи ключей компилятора (обычно это значение равно 1, потому что при этом в сохраненном файле не будет пустых мест) нехорошо, потому что это может снизить скорость выполнения программы. Есть еще один способ указания компилятору размера выравнивания, он заключается в использовании директивы препроцессора #pragma. Это не оговорено стандартом, но обычно есть директива #pragma pack, позволяющая сменить выравнивание для определенного отрезка исходного текста. Выглядит это обычно примерно так:



1 ... 124 125 126 [ 127 ] 128 129 130 ... 210

© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки.
Яндекс.Метрика