Добавил:
ivanov666
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз:
Предмет:
Файл:Архитектура вычислительных систем и Ассемблер с приложением методических указаний к лабораторным работам. Учебное пособие
.pdf
6.4. Взаимодействие со структурами данных 281
Обращение к полю структуры
Адрес поля структуры равен сумме адреса структуры
поля
displacement
. Для обращения к полю структуры необходимо разыменовать
base
и смещения нужного
его адрес ∗base + displacement.
Это соответствует косвенной адресации с двумя компонентами — базой
и смещением displacement, что в GAS обозначается displacement(base).
В листинге 6.39 показана инициализация полей
TagиVal
структуры
TSomeStruct, описанной в листинге 6.37.
Листинг 6.39. Инициализация структуры с выравниванием на 4 байта
1 TSomeStruct s;
2 asm
3 (
4 "movb␣$’a’,␣(%[S])\n"
5 "movl␣$13,␣4(%[S])\n"
6 :
7 :[S]"r"(&s)
8 : "memory"
9 );
10 cout << s.Tag << "␣" << s.Val << endl;
Вывод программы показывает корректность инициализации
a13
Так как расположение полей зависит от настроек компиляции, более надёжно
передавать смещения полей как параметры вставки (листинг 6.40).
base
Листинг 6.40. Инициализация структуры с неизвестным выравниванием
1 asm
2 (
3 "movb␣$’a’,␣%c[tag_disp](%[S])\n"
4 "movl␣$13,␣␣%c[val_disp](%[S])\n"
5 :
6 :[S]"r"(&s),
7 [tag_disp]"i"(reinterpret_cast<char *>(&s.Tag)
8 - reinterpret_cast<char *>(&s)),
9 [val_disp]"i"(reinterpret_cast<char *>(&s.Val)
10 - reinterpret_cast<char *>(&s))
11 : "memory"
12 );

282 Глава 6. Программирование на языке Ассемблера
Полученный код не зависит от настроек выравнивания, но очень тяжело чита-
ется.
Если код оформляется не как ассемблерная вставка, а как функция, принимающая структуру, описанную на C++, необходимо либо передавать смещения полей
как параметры функции и рассчитывать адреса вручную (что сильно замедлит
выполнение), либо задаться конкретными значениями (возможно, при помощи макросов препроцессора), либо, что лучше всего, описывать структуру таким образом,
чтобы при любых настройках выравнивания отсутствовали дыры между полями
и не менялись смещения полей (аналогично листингу 6.38).
Контрольные вопросы
1. Какая команда передаёт управление подпрограмме?
2. Какая команда возвращает управление вызывающей программе?
3. Что такое адрес возврата?
4. Какие вы знаете соглашения о вызове?
5. Как импортировать ассемблерную функцию в проект на C++?
6. Как, согласно ЕСПД, изображается блок «терминатор»?
7. Как, согласно ЕСПД, изображается блок «процесс»?
8. Как, согласно ЕСПД, изображается блок «решение»?
9. Как располагаются в памяти элементы массива?
10. Как найти размер массива, зная размер элемента и их количество?
11. Что такое выравнивание полей структуры?
12. Зачем нужно выравнивание данных?

Глава 7. Программирование на языке высокого уровня: С++
Два программиста быстро находят общий язык.
Как правило, это С++.
Программистский фольклор
Для разработки программного обеспечения часто используется язык общего
назначения C, а также разработанный на его основе объектно-ориентированный
язык C++.
Программированию на C++ посвящён отдельный курс. Вспомним некоторые
особенности этого языка, полезные при исследовании содержимого оперативной
памяти или сочетании программы на C++ с ассемблерными функциями или вставками.
Синтаксис языков C и C++ стандартизирован. В настоящее время существует
четыре стандарта языка C:
– C89 (ANSI C) ANSI X3.159-1989;
– C90 ISO/IEC 9899:1990;
– C99 ISO/IEC 9899:1999, последняя правка от 2007-11-15;
– C11 ISO/IEC 9899:2011 от 2011-12-19.
Доступ к текстам стандартов платный. Последний бесплатно доступный черновик
C11 — n1570 от 2011-04-12.
Также существует четыре стандарта C++. Последний, C++17, опубликован
в декабре 2017 г. Начато обсуждение пятой версии — C++20.
– C++98 ISO/IEC 14882:1998;
– C++11 ISO/IEC 14882:2011;
– C++14 ISO/IEC 14882:2014;
– C++17
ме. Некоторые компиляторы (особенно это касается коллекции Microsoft Visual
Studio) реализованы с нарушениями стандарта. Кроме того, многие компиляторы
дополнительно поддерживают не описанные в стандарте языка расширения, такие,
как OpenMP.
ные для всех компиляторов.
ISO/IEC 14882:2017 (последний бесплатно доступный черновик —
n4659
от 2017-03-21).
Не все компиляторы поддерживают последние стандарты в полном объё-
В данном пособии рассматриваются основные возможности языка, реализован-

284 Глава 7. Программирование на языке высокого уровня: С++
7.1. Структура программы
Но вот Эм шагает в область сильного слова «Могу».
Слушайте, слушайте моговест мощи!
В. Хлебников. Зангези
Выполнение программы на C или C++ начинается с функции
main()
. Она
называется головной, стартовой или главной функцией программы.
Функция
должна быть определена как
main()
описана в разделе [
int main()
basic.start.main
или как
int main(int, char ∗∗)
] стандарта C++. Она
. Обычно
используются обозначения int main(int argc, char ∗argv[]).
Функцию
так. С точки зрения компоновщика, точка входа — это метка
на C/C++ по адресу
main()
иногда называют точкой входа в программу, но это не совсем
_start
. Для программ
_start
находится стартовый код библиотеки libc, который,
в частности, инициализирует все используемые библиотекой ресурсы и глобальные
объекты, готовит параметры для стартовой функции
main()
, вызывает её, а после
возврата управления из main() завершает программу.
При вызове исполняемого файла программы ему часто передаются так называемые параметры командной строки. Обычно это имя обрабатываемого файла
(например,
pdflatex paper.tex
) или настройки для работы (
ls -la
) В частности, щелчок мыши в графической оболочке по файлу с данными эквивалентен
запуску какой-либо программы с именем щёлкнутого файла в качестве параметра.
Параметры командной строки разделяются пробелами; например, здесь
grep str */*.tex *.tex
программа
str
grep
(поиск в текстовых файлах) вызывается с тремя параметрами —
(искомая строка),
*/*.texи*.tex
(имена файлов для поиска). Нулевым
параметром командной строки считается само имя исполняемого файла.
Общее количество параметров, включая имя исполняемого файла — это
первый аргумент функции
main()
. Массив строк
argv
содержит сами эти пара-
argc
метры.
Возвращаемое значение функции
main()
— код завершения программы. Он
равен нулю в случае успешного завершения и ненулевому коду ошибки в противном
случае.
Корректный код завершения позволяет оболочке учитывать итоги выполнения
программы. В частности, следующий однострочный скрипт оболочки
,
pdflatex paper && bibtex paper
запускает программу
bibtex
(сборка библиографии) только в том случае, если
программа pdflatex (сборка текста) корректно завершилась.

7.2. Типы данных 285
7.2. Типы данных
На данные свои взирая объективно,
Задумал типыяиидеалсоздал...
К. П. Прутков. Безвыходное положение
Базовые типы C++ описаны в разделе Fundamental types ([
стандарта C++ [25]. Раздел Types ([
basic.types
]) описывает общие характеристики
basic.fundamental
хранения данных в памяти.
7.2.1. Целые типы
Чтобы мы не увидели войну людей, шашек Азбуки,
а услышали стук длинных копий Азбуки.
В. Хлебников. Зангези
Существует пять стандартных знаковых целых типов:
– signed char;
– signed short int (синонимы: short, signed short);
– signed int (синонимы: int, signed);
– signed long int (синонимы: long, signed long);
– signed long long int (синонимы: long long, signed long long);
и пять соответствующих
беззнаковых целых типов
(каждый из них имеет тот же
размер и те же требования к выравниванию, что и соответствующий знаковый):
– unsigned char;
– unsigned short int (синонимы: unsigned short);
– unsigned int (синонимы: unsigned);
– unsigned long int (синонимы: unsigned long);
– unsigned long long int (синонимы: unsigned long long).
Тип
char
, в зависимости от реализации, может быть знаковым или беззнаковым.
char,signed charиunsigned char
Типы
имеют один размер и одинаковые
требования к выравниванию.
Стандарт C++ не содержит явных значений разрядности типов.
Согласно стандарту,
char,signed charиunsigned char
занимают 1 байт.
При этом, если байт на используемой программно/аппаратной платформе не восьмибитен, то и
char
однобайтовый, но не однооктетный, то есть занимает не 8 бит.
Таким образом, всякий объект любого типа (обозначим егоT) может быть
скопирован в массив char [sizeof(T)].
])

286 Глава 7. Программирование на языке высокого уровня: С++
В ряду целых типов каждый следующий тип имеет размер (и диапазон значений)
не меньше предыдущего:
sizeof(char) sizeof(short) sizeof(int) sizeof(long)
sizeof(long long)
Размеры стандартных целых типов C++ также должны соответствовать ограничениям раздела 5.2.4.2.1 стандарта C [20]. В этом разделе описаны значения, которые
обязательно должны быть включены в диапазоны значений соответствующих типов
(при этом указанные значения не обязательно должны быть граничными). Анализ этих значений приводит к следующим выводам о минимально допустимой
разрядности стандартных типов (таблица 7.1).
Минимальная разрядность стандартных целых типов
Таблица 7.1
Тип Разрядность, бит (не менее)
char 8
short 16
int 16
long 32
long long 64
Тип
int
должен соответствовать «естественной» разрядности архитектуры
(расплывчатость этой формулировки и то, что по умолчанию разрядность данных
в шестидесятичетырёхбитном режиме равна 32, приводит к тому, что практически
на 64-битной платформе тип int чаще всего 32-разряден).
Типы
char,signed charиunsigned char
символьными типами
, так как они могут быть интерпретированы не только как
называются ещё
узкими (narrow)
числа, но и как символы; соответственно их размер должен быть таким, чтобы
хранить представление любого символа из базового набора.
Единственное отличие узких символьных типов от других целых (кроме размера) — то, что операторы ввода/вывода в поток для них перегружены так, что
отображают не значение переменной, а символ, код которого равен этому значению.
Отображение чисел, не равных кодам символов ASCII, не определено и может
быть разным для различных реализаций,
Все арифметические операции для
char,signed charиunsigned char
вы-
полняются точно так же, как и для любого другого целого типа.
Для типа
unsigned char
каждой возможной комбинации разрядов должно
соответствовать отдельное число. Для других типов это не обязательно.

7.2. Типы данных 287
Для представления
wchar_t
, имеющий такой же размер, знаковость и требования к выравниванию, что
расширенного набора символов
введён специальный тип
и один из целых типов.
Тип bool может хранить только два значения — true и false.
Практическая реализация
Всё, что явно не прописано в стандарте C++, может быть реализовано по-разному
на различных платформах.
Всё, написанное в этом разделе и в аналогичных разделах ниже, описывает в основном платформу x86 и наиболее популярные компиляторы. На других программно/аппаратных платформах (в частности, при использовании экзотического
компилятора) эти закономерности могут быть нарушены.
Для представления беззнаковых чисел используется натуральный двоичный код
(см. раздел 2.4 настоящего пособия); знаковые представляются дополнительным
кодом (см. разделы 2.4–2.5). Таким образом, для всех целых типов, в частности, для
unsigned char
, каждой возможной комбинации разрядов соответствует
отдельное число.
Для знаковых и беззнаковых типов по-разному реализованы некоторые арифметические и битовые операции, в частности, умножение (оператор*), деление
/
(оператор получения частного
битовые сдвиги (операторы
и оператор получения остатка от деления%),
<<,>>
), расширение при присваивании (если приёмник
больше источника).
Беззнаковые целые типы должны подчиняться циклической арифметике по
модулю 2N.
char, wchar_t
не являются синонимами, хотя вычисления с их использованием компилиру-
char
ются в одинаковые конструкции. Именно, при перегрузке
Тип
char
— восьмибитный и знаковый. При этом
charиsigned
f(char)иf(signed
char) считаются разными функциями и их имена декорируются по-разному (для
сравнения,
f(int)иf(signed int)
не различаются и декорируются одинаково).
Базовым набором символов является ASCII. При интерпретации переменной
типа
char
как символа значение этой переменной трактуется как ASCII-код. Таким
образом, 64, 0x40 и ’@’ — это разные формы записи одного и того же числа (ASCIIкод символа «собака» равен шестидесяти четырём).
Символы не из ASCII, в частности, русские буквы, в зависимости от реализации,
могут быть представлены одной переменной типа
cp866; в этом случае с учётом знаковости
трактуются как отрицательные, то есть
char
’ы’ < 0 < ’s’
char
(кодировки koi8, cp1251,
коды русских букв — 128–255 —
) или цепочкой из несколь-

288 Глава 7. Программирование на языке высокого уровня: С++
ких
char
’ов (кодировка UTF-8; в этом случае можно описать строку из русских
букв как char [], но невозможно описать одну такую букву как char).
Понятие расширенного набора символов и тип
Unicode. Широкий символьный тип
wchar_t
байтов. Как правило, при хранении символьной информации в
wchar_t
возникли с появлением
может содержать любое количество
wchar_t
используются такие юникодные кодировки или их части, в которых каждый логический
символ занимает не менее одного
wchar_t
(UTF-32, или, если поддерживается
только часть набора символов Unicode — UTF-16). Расширенный набор символов,
соответственно — та часть набора символов Unicode, которая поддерживается
и может быть записана одним wchar_t.
Литералы, соответствующие широким строкам и символам, предваряются префиксомL. Для ввода/вывода широких символов и строк используются те же операторы, что и для узких, но другие потоки (
wchar_t
отображает число, при выводе
wcin/wcout
wchar_t*
). Поток
cout
при выводе
— адрес в шестнадцатеричном
виде.
Тип
int
на 16-разрядных платформах занимал 16 бит,
на 32-разрядных — 32 бита. На 64-битной платформе
int
чаще всего 32-разряден.
Тип short 16-разряден на 16-, 32- и 64-разрядных платформах.
Тип
long
на 16- и 32-разрядных платформах занимал 32 бита. На 64-битной
платформе — 64.
Тип long long, если поддерживается, 64-разряден.
Таким образом в C++ не существует гарантированно 32-разрядного фундаментального целого типа.
Кроме фундаментальных типов, описанных в стандарте C++, поддерживаются также типы, описанные в стандарте C [20]. Современный стандарт языка C
включает типы фиксированной разрядности, в частности, тридцатидвухразрядный
int32_t.
7.2.2. Вещественные типы
Страшен очерк их лиц: смуглого дико и нежно пространства.
Существует три стандартных вещественных типа:
– float;
– double;
– long double.
тип
double
обеспечивает не меньшую точность, чем
меньшую точность, чем double.
В. Хлебников. Зангези
float,long double
—не

7.2. Типы данных 289
Множество значений типа
типа
double
; множество значений типа
float
является подмножеством множества значений
double
является подмножеством множе-
ства значений типа long double:
float ⊆ double ⊆ long double
Все они хранят данные с плавающей запятой.
Практическая реализация
Вещественные числа представляются в форматах с плавающей запятой различной точности, в соответствии со стандартом двоичной арифметики с плавающей
точкой IEEE 754 [14, 87].
Процессоры семейства x86 имеют два набора команд для работы с вещественными числами с плавающей запятой. Это команды математического сопроцессора
FPU и команды расширений XMM и YMM.
Модуль операций с плавающей запятой процессоров семейства x86 (floating
point unit, FPU) поддерживает три типа вещественных чисел
– одинарной точности (32 бита);
– двойной точности (64 бита);
–
с двойной расширенной точностью (80 бит, внутренний нестандартный формат
FPU — 15 разрядов отводится под порядок, 64 под мантиссу).
Расширения XMM и YMM поддерживает два типа вещественных чисел — одинарной и двойной точности.
Типу
float
соответствует число одинарной точности, типу
Типу
long double
чаще всего соответствует 10-байтовое число расширенной точности. Размер выделяемой под переменную
ти при этом в зависимости от флагов компиляции (
и
-m128bit-long-double
в GCC), может быть равен 12 или 16 байт. Используются
double
long double
— двойной.
памя-
-m96bit-long-double
только первые 80 бит (10 байт), остальное — неиспользуемая память (заполнение).
В Microsoft Visual Studio типу
long double
соответствуют числа двойной
точности (64 бита), хотя long double не считается синонимом double.
7.2.3. Специальные типы
Волю осязаем как пустоту, как отсутствие преград,
как отрицательный объём для движения.
В. Хлебников. Мысли и заметки
Множество значений типа
void
пусто. Он, в частности, используется для
описания функций, которые не возвращают значения, так как в C++ нет отдельного
понятия процедуры или подпрограммы, не возвращающей значения.

290 Глава 7. Программирование на языке высокого уровня: С++
Любое выражение может быть приведено к типу void.
Указатель
void*
считается нетипизированным. К типу
void*
может быть
преобразован любой указатель.
7.2.4. Указатели
Пространство звучит через Азбуку.
В. Хлебников. Зангези
Каждому, простому или сложному, типу данныхTв C++ соответствует свой
тип указателяT*— адрес участка памяти, где лежит переменная типаTили массив
таких переменных.
Все указатели — целые беззнаковые величины одного размера. По разрядности
указателей в настоящее время определяется разрядность системы, так что на
32-битной платформе указатели занимают 32 бита, на 64-битной — 64.
Любой указатель может быть разыменован как массив, причём с использованием произвольного целого индекса. Таким образом, программист должен сам следить за количеством элементов по адресу, хранящемуся в переменной-указателе.
В C++ указатели на различные типы сами считаются различными типами. Соответственно, механизм типизации не позволяет неявно преобразовать указатели
на различные типы друг в друга (в частности,
приведение к нетипизированному указателю
int*вchar*
void*
. Это логично для строго ти-
). Возможно только
пизированного языка высокого уровня, но не всегда удобно для низкоуровневого
исследования данных.
Тем не менее, физически все указатели имеют одно строение — ячейка, где хранится адрес в памяти. Таким образом, указатели на разные типы могут быть преобразованы друг в друга с помощью оператора преобразования
либо в два этапа через
static_cast
приводится к нетипизированному указателю
void*
. Вначале исходный тип неявно или с помощью
reinterpret_cast
void*
, затем с помо-
щью static_cast нетипизированный указатель приводится к требуемому типу.
Также с указателями связано два целых типа той же разрядности. Это знаковая
разность указателей — ptrdiff_t и беззнаковая длина массива — size_t.
В C/C++ нет фундаментального строкового типа, хотя есть строковые литералы. Функции стандартной библиотеки C обрабатывают как строки указатели
char
на массивы чисел типа
. Признаком конца строки служит нулевое значение
очередного элемента массива (символ с кодом, равным нулю).
Указатель на массив чисел типа
wchar_t
, завершающийся элементом, равным
нулю, также может быть интерпретирован как строка (так называемые «широкие»
строки). Для их обработки используются функции с префиксом w.
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]
