Последние записи
- Windows 10 сменить администратора
- Рандомное слайдшоу
- Событие для произвольной области внутри TImage
- Удаление папки с файлами
- Распечатка файла
- Преобразовать массив байт в вещественное число (single)
- TChromium (CEF3), сохранение изображений
- Как в Delphi XE обнулить таймер?
- Изменить цвет шрифта TextBox на форме
- Ресайз PNG без потери прозрачности
Интенсив по Python: Работа с API и фреймворками 24-26 ИЮНЯ 2022. Знаете Python, но хотите расширить свои навыки?
Slurm подготовили для вас особенный продукт! Оставить заявку по ссылке - https://slurm.club/3MeqNEk
Online-курс Java с оплатой после трудоустройства. Каждый выпускник получает предложение о работе
И зарплату на 30% выше ожидаемой, подробнее на сайте академии, ссылка - ttps://clck.ru/fCrQw
20th
Май
Взаимодействие с сетевыми интерфейсами в Linux
Linux программирование. Начинающим
Этой статьей я бы хотел открыть цикл публикаций, связанных с самым интересным и захватывающим в Linux – программировании. Я не собираюсь рассказывать о том, в чем писать программы, как их компилировать и запускать, информацию по данным вопросам найти очень легко и я думаю, что любой с этим справится. Языки программирования, которые будут использоваться в статьях: C, C++, bash script.
Олег Кутков http://www.programmersforum.ru/member.php?u=2583
Oleg Kutkov by elenbert@gmail.com
В последнее время программисты и простые пользователи начинают проявлять все больший интерес к Unix- подобным операционным системам, в частности к Linux. К сожалению, новичкам, не всегда просто разобраться в новой среде, даже не смотря на то, что Linux считается одной из самых хорошо документированных ОС. Информация, как правило, разбросана по форумам, множеству отдельных статей и блогов. Основное содержимое данных материалов касается администрирования и настройки дистрибутивов, программистам же, как правило, приходится довольствоваться man-документацией ил
и автоматической doxygen-документацией (документация, сгенерированная автоматически, на основе комментариев в исходном коде). К тому же, как это часто бывает – наиболее интересный материал на английском языке. Безусловно, данную ситуацию следует исправлять.
Начало. Общие сведения
Программировать в Linux очень просто и легко. Для программистов созданы практически идеальные условия: множество мощных инструментов, открытые исходные коды, сама организация системы, множество фреймворков. Работа с файлами, строками, массивами, классами, контейнерами,
в Unix- среде, практически ничем не отличается от таковой в Windows, это касается и множества других стандартных функций и библиотек. Различия начинаются на более низком уровне. Предлагаю разобраться, как работать с сетевыми интерфейсами.
Как известно, сетевые интерфейсы Linux обозначаются короткими строковыми именами – eth0, wlan0, lo и т.д. Интерфейсу можно присвоить любое удобное имя, но лучше руководствоваться общепринятыми правилами именования. Думаю, что ни для кого не секрет, что все устройства в Linux представлены в виде особых файлов в каталоге /dev, это справедливо для всех устройств, кроме сетевых адаптеров. Но так было не всегда, в прошлых версиях Linux ядра были доступны устройства /dev/eth0, /dev/tap0 и т.д., в более же новых ядрах эти устройства упразднили, и сетевые интерфейсы были перенесены в п
амять в так называемое пространство сокетов
Примечание.
* Следует сказать, что в другой популярной Unix – подобной ОС – FreeBSD по-прежнему, сохранен старый способ. Поэтому, если вы захотите переносить приложения с Linux на FreeBSD – следует учитывать это и другие мелкие различия. Но это не значит, что работа с этими устройствами каким-либо образом осложнилась, все очень и очень просто.
Далее я бы хотел рассмотреть особую и очень важную функцию, обеспечивающую обмен управляющими сообщениями между устройством и пользовательским приложениями.
Интерфейсы управления
Для взаимодействия с устройствами, а на самом деле с драйверами устройств, в Unix имеется особый вызов – ioctl, означающий Input-Output Control.
Справедливости ради, следует сказать, что Windows имеется подобный интерфейс – DeviceIoControl. Для использования данного вызова следует включить заголовочный файл <sys/ioctl.h>. Существует также возможность определять свои собственные ioctl вызовы, этим пользуются разработчики драйверов. Рассмотрим вызов ioctl детально…
int ioctl(int d, int request, …);
d – это открытый файловый дескриптор устройства.
request – это тип запроса, для различных устройств запросы различные, соответствующую информацию обычно легко найти в справочных материалов.
Примечание.
В этой статье я рассмотрю все типы запросов для сетевых устройств.
третий аргумент – это указатель на void, т.е. там могут оказаться какие угодно данные, в зависимости от того, что требует конкретный тип запроса. В случае успеха вызовом возвращается ноль. В случае ошибки возвращается «-1» и значение глобальной переменной errno устанавливается соответствующим образом. Чтобы было понятнее, пример использования:
#include <termios.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <errno.h>
int main()
{
int fd, serial, res; // дескриптор, параметр, результат
fd = open("/dev/ttyS0", O_RDONLY); // открываем устройство
if (fd < 0) { // проверяем дескриптор и в случае ошибки - выводим ее пользователю
printf("Открытие /dev/ttyS0 завершилось с ошибкой: %s\n", strerror(errno));
return 1;
}
res = ioctl(fd, TIOCMGET, &serial); // выполняем вызов ioctl с запросом TIOCMGET
if (res < 0) { // проверяем результат и в случае ошибки выводим
printf("Вызов ioctl завершился с ошибкой: %s\n", strerror(errno));
return 1;
}
if (serial & TIOCM_DTR) // проверяем результат
printf("Последовательный порт не готов\n");
else
printf("Последовательный порт готов\n");
close(fd); // закрываем дескриптор
return 0;
}
Сначала мы подключаем необходимые заголовочные файлы, в которых объявлены используемые нами функции, а так же ioctl запросы. В данном примере идет работа с последовательным портом компьютера, а точнее проверяется готовность приема данных. Вызов ioctl на открытом файловом дескрипторе, передает драйверу открытого устройства команду TIOCMGET, сохраняет и возвращает результат в переменную
Serial.
Аналогично происходит процесс передачи информации для драйвера из переменной параметра. Как уже говорил, тип переменной параметра зависит от типа запроса. Как правило, это специальная структура. Таков общий принцип работы ioctl, как видим – ничего сложного. Теперь перейдем непосредственно к теме обсуждения – сетевым интерфейсам…
Работа с сетевыми интерфейсами
Выше был рассмотрен пример использования вызова ioctl для получения данных из открытого дескриптора последовательного порта. Для сетевых интерфейсов работа выполняется аналогичная. Внимательный читатель мог обратить внимание, как выше я говорил о том, что для сетевых устройств не существует таких специальных файлов, соответственно eth0 нельзя открыть также, как и последовательный порт. Это так сетевые устройства перенесены в пространство сокетов и для доступа к этим устройствам следует использовать именно дескрипторы сокетов. Открытие сокета для упр
авления сетевым устройством выполняется так:
int sock = socket(AF_INET, SOCK_DGRAM, 0);
Результат sock и есть дескриптор сокета, перед использованием, его, как и все прочие дескрипторы, следует проверять на отрицательные значения, на случай возможных ошибок. Также, для ioctl вызовов на дескрипторе сокета применяется особая структура параметра (serail, в примере выше) – struct ifreq. Это очень важная структура, используемая во всех случаях работы с сетевыми устройствами. Разберем ее подробнее:
struct ifreq {
char ifr_name[IFNAMSIZ];
union {
struct sockaddr ifr_addr;
struct sockaddr ifr_dstaddr;
struct sockaddr ifr_broadaddr;
struct sockaddr ifr_netmask;
struct sockaddr ifr_hwaddr;
short ifr_flags;
int ifr_ifindex;
int ifr_metric;
int ifr_mtu;
struct ifmap ifr_map;
char ifr_slave[IFNAMSIZ];
char ifr_newname[IFNAMSIZ];
char * ifr_data;
};
};
Структура состоит из двух полей: имени интерфейса и объединения, каждое возможное поле, которого выражает конкретный параметр сетевого интерфейса. Данная структура позволяет, как получать параметр интерфейса, так и задавать его. Так как используется объединение – можно получать и задавать только
один параметр за раз.
Интересуемые и используемые поля:
ifr_addr – IP адрес интерфейса
ifr_dstaddr – адрес сервера (для Point-to-Point соединения)
ifr_broadaddr – широковещательный адрес интерфейса
ifr_netmask – маска подсети
ifr_hwaddr – mac адрес
ifr_ifindex – индекс интерфейса (внутри ядра сетевые интерфейсы имеют уникальные индексы, для упращения работы сетевой подсистемы)
ifr_flags – различные флаги (интерфейс поднят или опущен, интерфейс активен или неактивен и др.)
ifr_metric – метрика интерфейса
ifr_mtu – mtu интерфейса
ifr_map – структура, содержащая в себе техническую информацию (номер прерывания, память устройства и т.д.)
ifr_slave – подчиненное устройство
ifr_newname – новое имя интерфейса (для переименования)
Перед любым использованием структуры следует ее обязательно обнулять с помощью memset, а затем задавать имя интересуемого интерфейса ifr_name. Теперь перейдем от теории к действию – получим IP адрес интерфейса eth0! Соответствующий пример приведен ниже:
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <string.h>
#include <net/if.h>
#include <errno.h>
#include <stdio.h>
int main()
{
int sock; // дескриптор сокета
struct sockaddr_in *in_addr; // структура интернет-адреса (поля)
struct ifreq ifdata; // структура - параметр
char *ifname = "eth0"; // имя интерфейса
sock = socket(AF_INET, SOCK_DGRAM, 0); // открываем дескриптор сокета
if (sock < 0) {
printf("Не удалось открыть сокет, ошибка: %s\n", strerror(errno));
return 1;
}
memset(&ifdata, 0, sizeof(ifdata)); // очищаем структуру
strncpy(ifdata.ifr_name, ifname, sizeof(ifname)); // задаем имя интерфейса
//получаем айпи адрес с помощью SIOCGIFADDR, одновременно проверяя результат
if (ioctl(sock, SIOCGIFADDR, &ifdata) < 0) {
printf("Не получить IP адрес для %s, ошибка: %s\n", ifname, strerror(errno));
close(sock);
return 1;
}
in_addr = (struct sockaddr_in *) &ifdata.ifr_addr; // преобразовываем из массива байт
// в структуру sockaddr_in
printf("Интерфейс %s IP адрес: %s\n", ifname, inet_ntoa(in_addr->sin_addr));
close(sock);
return 0;
}
В этом коде я ввел одну новую структуру и одну новую функцию. Начну со структуры:
struct sockaddr_in {
short sin_family;
unsigned short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
Даная структура предназначена для хранения базовых данных об адресе сетевого узла. Назначение ее полей:
sin_family - семейство адресов, может иметь два значения: AF_INET для IPv4 и AF_INET6 для IPv6
sin_port - порт узла
sin_addr - структура адреса (о ней ниже)
sin_zero - этот массив можно использовать по своему усмотрению
struct in_addr {
unsigned long s_addr; // load with inet_pton()
};
Эта структура состоит всего из одного поля – числа, представляющего собой собственно IP адрес. Например, «192.168.8.98», в таком формате, имеет вид 1644734656. Функция inet_ntoa предназначена для преобразования такого числового значения в привычный цифро-точечный формат. В качестве аргумента, функция принимает struct in_addr, а возвращает указатель на строку.
Теперь скажу о преобразовании из массива байт в структуру sockaddr_in. Как было показано выше, поле ifr_addr, в структуре ifreq, имеет тип struct sockaddr. Эта структура является своего рода, «упрощенной» структурой sockaddr_in.
struct sockaddr {
unsigned short sa_family;
char sa_data[14];
};
У нее всего два поля: семейство адресов и массив 14 байт, содержащий собственно адрес. Структуры sockaddr и sockaddr_in хорошо и естественно приводятся к типу друг друга, чем мы и воспользовались. Обратная операция – задание адреса выполняется примерно так же. Отличия только два: перед вызовом ioctl, полю ifr_addr нужно задать новое значение адреса, а тип запроса будет SIOCSIFADDR. Как видно, оба запроса SIOCSIFADDR и SIOCGIFADDR отличаются на одну букву, которая означает Set и Get, соответственно.
Я не буду приводить пример, показывающий, как задавать новое значение адреса, так как предоставленных сведений уже достаточного для того, что бы читатель разобрался сам. Дам лишь небольшую подсказку: для преобразования строкового значения адреса, например «192.168.8.98», в тип struct in_addr следует применять функцию inet_aton:
inet_aton(const char *saddr, struct in_addr *iaddr);
saddr – указатель на строку с адресом
iaddr – указатель на struct in_addr
Для получения (или задания) всех остальных параметров используется аналогичный способ, отличие лишь в типе запроса и использовании соответствующего поля в объединении структуры ifreq. Список всех возможных типов запроса для получения или задания параметров сетевого интерфейса:
SIOCGIFNAME – получить имя сетевого интерфейса
SIOCGIFINDEX – получить индекс сетевого интерфейса
SIOCGIFFLAGS, SIOCSIFFLAGS – получить/задать флаг интерфейса (о флагах ниже)
SIOCGIFMETRIC, SIOCSIFMETRIC – получить/задать метрику интерфейса
SIOCGIFMTU, SIOCSIFMTU – получить/задать mtu интерфейса
SIOCGIFHWADDR, SIOCSIFHWADDR – получить/задать mac адрес
SIOCGIFMAP, SIOCSIFMAP – получить/задать аппаратные параметры (struct ifmap)
Наиболее интересные флаги интерфейса:
IFF_UP – интерфейс запущен
IFF_BROADCAST – интерфейс является широковещательным
IFF_LOOPBACK – интерфейс является петлевым
IFF_POINTOPOINT – point-to-point интерфейс
IFF_RUNNING – интерфейс активен
IFF_MULTICAST – интерфейс поддерживает многоадресность
Небольшой пример использования флагов, получение информации о том, запущен ли интерфейс:
ioctl(sock, SIOCGIFFLAGS, &ifdata);
if (ifdata.ifr_flags && IFF_UP) {
printf("Сетевой интерфейс %s запущен\n", ifdata.ifr_name);
}
else {
printf("Сетевой интерфейс %s не запущен\n", ifdata.ifr_name);
}
На данном этапе предлагаю читателю самостоятельно написать небольшое приложение, получающее, в качестве аргумента командной строки, имя интерфейса и выводящее всю информацию о нем. Но как быть, когда заранее неизвестны имена интерфейсов, как получить просто список доступных сетевых интерфейсов? Очень просто. В помощь приходит замечательный вызов if_nameindex().
Небольшой пример, показывающий как получить список всех сетевых интерфейсов и их индексов.
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <string.h>
#include <net/if.h>
#include <errno.h>
#include <stdio.h>
int main()
{
int sock; // дескриптор сокета
struct sockaddr_in *in_addr; // структура интернет адреса (поля)
struct ifreq ifdata; // структура - параметр
struct if_nameindex* ifNameIndex; // структура интерфейсов и их индексов
sock = socket(AF_INET, SOCK_DGRAM, 0); // открываем дескриптор сокета
if (sock < 0) {
printf("Не удалось открыть сокет, ошибка: %s\n", strerror(errno));
return 1;
}
ifNameIndex = if_nameindex();
if (ifNameIndex) { // если удалось получить данные
while (ifNameIndex->if_index) { // пока имеются данные
memset(&ifdata, 0, sizeof(ifdata)); // очищаем структуру
strncpy(ifdata.ifr_name, ifNameIndex->if_name, IFNAMSIZ); // получаем имя следующего интерфейса
// получаем IP адрес с помощью SIOCGIFADDR, одновременно проверяя результат
if (ioctl(sock, SIOCGIFADDR, &ifdata) < 0) {
printf("Не получить IP адрес для %s, ошибка: %s\n", ifdata.ifr_name, strerror(errno));
close(sock);
return 1;
}
// преобразовываем из массива байт в структуру sockaddr_in
in_addr = (struct sockaddr_in *) &ifdata.ifr_addr;
printf("Интерфейс %s индекес %i IP адрес: %s\n", ifdata.ifr_name, ifNameIndex->if_index, inet_ntoa(in_addr->sin_addr));
++ifNameIndex; // переходим к следующему интерфейсу
}
}
close(sock);
return 0;
}
Данный пример является доработанной версией предыдущего примера, выводя все доступные интерфейсы, их индексы и IP адреса. На рисунке ниже изображен процесс компиляции и запуска приложения с выводом всей информации. Видно, так же, сообщение об ошибке, для виртуального интерфейса, с которого не удалось получить информацию:
Заключение
В данной статье я постарался раскрыть основные аспекты взаимодействия с сетевыми интерфейсами в OC Linux, многое из этого будет справедливо и для других Unix подобных операционных систем. Надеюсь, что статья получилась интересной и полезной.
Напоследок не могу не сказать, что в Linux, работа с сетью через ioctl является устаревшим способом, на смену которого приходит Netlink. Netlink – это модуль, состоящий из двух частей, одна находится в ядре и взаимодействует непосредственно с соответствующими подсистемами, вторая – библиотека на уровне пользователя. Обе части могут обмениваться информацией между собой. Это очень мощный и удобный способ управлениями всеми параметрами сетевой подсистемы Linux, такие издания, как «Linux journal» рекомендуют пользоваться только Netlink. К сожалению, документации по данному API н
е так много, как хотелось, и приходится собирать по крупицам. Но в будущих статьях я постараюсь рассмотреть некоторые аспекты использования Netlink, так как уже имею опыт данной сфере. До встречи на страницах журнала!
Литература
. Исходные коды пакета утилит net-tools
. Doxygen документация
. Christian Benvenuti. Understanding Linux network internals. – O’reilly Media, 2005
http://www.linbai.info/computers-it/understanding-linux-network-internals.html
. Интернет ресурсы: www.linuxjournal.com, www.stackoverflow.com, различные форумы
Это статья с третьего выпуска журнала “ПРОграммист”.
Скачать этот выпуск можно по ссылке.
Ознакомиться со всеми номерами журнала.
Обсудить на форуме — Взаимодействие с сетевыми интерфейсами в Linux
20th
Надувные колонки
Надувные колонки как альтернативу привычным разработала компания Cebop. Приведение аксессуара в рабочее состояние занимает всего лишь пару минут. Колонки имеют стильный дизайн и оснащены встроенным усилителем и регулятором громкости, выходная мощность их равна 10 Вт. Цена от 35 евро.
Это заметка с третьего выпуска журнала “ПРОграммист”.
Скачать этот выпуск можно по ссылке.
Ознакомиться со всеми номерами журнала.
20th
Сенсорная кожа
Сенсорную кожу разработали ученые из университета Карнеги-Меллон совместно с компанией Microsoft. Технология Skinput позволяет превратить кожу человека в подобие сенсорного экрана. Точнее говоря, проводя или нажимая пальцем по поверхности кожи ладони или предплечья, можно, например, контролировать звонки и писать сообщения, набирать телефонный номер, проигрыв
ать музыку или играть хоть в тетрис. Система может реагировать даже на незаметные движения, такие как щипок или дерганье мыщцы. Меню проецируется на руку, а нажатия улавливает и интерпретирует специальный браслет с пьезоэлектрическими датчиками на верхней части руки.
Это заметка с третьего выпуска журнала “ПРОграммист”.
Скачать этот выпуск можно по ссылке.
Ознакомиться со всеми номерами журнала.
20th
ICQ куплена за $187,5 млн
ICQ куплена за $187,5 млн инвестиционным фондом Digital Sky Technologies, совладельцем которого является российский предприниматель Алишер Усманов. Digital Sky Technologies владеет контрольным пакетом акций Mail.ru (53,2%) и Astrum Online Entertainment, 30% социальной сети Vkontakte.ru, 25% в системе электронных платежей OE Investments, 100% портала HH.ru и 70% эстонской Forticom (основного владельца Odnoklassniki.ru), а также небольшой долей акций социальной сети Facebook (3,5%).
Это заметка с третьего выпуска журнала “ПРОграммист”.
Скачать этот выпуск можно по ссылке.
Ознакомиться со всеми номерами журнала.
20th
Журнал “ПРОграммист” – третий выпуск
Дорогие друзья!
Добро пожаловать в майский выпуск журнала «ПРОграммист» от клуба программистов www.programmersforum.ru. Сегодня у нас праздник. Благодаря Василию Мединцеву, Алексею Шульге и Егору Горохову у журнала появился еще один небольшой, но уютный домик. Милости просим погостить в нашей резиденции http://procoder.info. Ну и какое-же новоселье без подарков. В Редакции пополнение ведущим рубрики новостей Антоном Бердниковым и женской половиной. Рады представить Вам Ксению, второго литредактора нашего журнала…
В этом выпуске жаждущих читателей порадует новым практическим материалом Владимир Дегтярь по использованию компьютера в качестве управляющего контроллера. Несомненно, не забыл он и о продолжении серии уроков по графике. Александр Терлецкий дебютирует со статьей по созданию спектрограммы для движка BASS. Utkin поможет найти кратчайший путь и выведет оптимальный маршрут. Олег Кутков статьей по особенностям взаимодействия с сетевыми интерфейсами не оставит равнодушными фанатов Linux-а. Рубрику «Лаборатория» на это раз поддержал Алек
сандр Уколов с материалом по практике приема и передачи звука по сети. Программный прототип VoIP телефона не за горами.
Еще одним подарком к новоселью стала информационная поддержка нашего журнала со стороны Международной Академии Информатизации МАИН РК www.akademy.kz и журналом «Радиолюбитель» www.radioliga.com. Еще раз спасибо Василию www.kotoff.info и Вам, дорогие читатели, за ваш интерес к журналу и ценные советы!
ccылки для скачивания (в архиве PDF и ресурсы к статьям):
http://programmersclub.ru/pro/pro3.zip
http://procoder.info/index.php?type=download (тут же и DjVU)
http://raxp.radioliga.com/cnt/s.php?p=pro3.zip
С уважением, Редакция
20th
Нанопечать RFID-меток
Нанопечать RFID-меток возможно станет альтернативой штрих-коду на товарах. До настоящего времени радиочастотные метки изготавливались на кремниевой основе. Использование пластика или бумаги в качестве исходного материала, а также поддержка «рулонной» печати поможет многократно сократить затраты на производство этих компонентов. Ключевым компонентом новой технологии являются особые чернила для струйного принтера, содержащие углеродные нанотрубки. Эти чернила используются для создания тонкопленочных транзисторов, которые в свою очере
дь являются основой для пассивных RFID-меток, содержащих до 16 бит информации и наносимых на бумагу или пластик методом печати.
Это заметка с третьего выпуска журнала “ПРОграммист”.
Скачать этот выпуск можно по ссылке.
Ознакомиться со всеми номерами журнала.
20th
Телефон читающий по губам
Телефон, читающий по губам продемонстрировала на выставке CeBIT 2010 группа исследователей из технологического института города Карлсруэ (Германия). Технология позволит владельцам сотовых телефонов обмениваться информацией по телефону, не издавая ни единого звука.
Устройство использует методику электромиографии (применяется для диагностики некоторых нервных заболеваний), то есть измеряет электрическую активность движений мышц лица во время разговора. Крошечные электрические сигналы, производимые лицевыми мышцами, оно записывает даже тогда, когда человек вообще беззвучно воспроизводит слова. В настоящее время для снятия показаний используется 9 электродов, закрепляемых на коже, но в будущем ученые надеются избавиться от проводов и эти электроды будут встроены в мобильные устройства. Технолог
12th
Май
Введение в SSE
SSE – FPU XXI века
Автор: Ivan32
Аннотация:
В данной статье рассматриваются базовые принципы работы с расширением SSE.
Введение:
С момента создания первого математического сопроцессора(FPU) для х86-процессоров, прошло уже около 30 лет.
Целая эпоха технологий и физических средств их воплощения прошла, с тех пор, и нынешние FPU стали на порядок быстрее, энергоэффективней и компактней того первого FPU – 8087. С тех пор FPU стал частью процессора, что, конечно же, положительно сказалось на его производительности. Тем не менее, нынешняя скорость выполнения команд FPU оставляет желать лучшего.
К счастью это лучшее уже есть. Им стала технология под названием SSE.
Аппаратное введение:
SSE – Streaming SIMD Extensions – был впервые представлен в процессорах серии Pentium III на ядре Katamai.
SIMD – Single Instruction Multiple Data. Аппаратно данное расширение состоит из 8(позже 16, для режима Long Mode-x86-64) и конечно контрольного регистра – MXCSR
В последующих расширениях SSE2 SSE3 SSSE3 SSS4.1 и SSE4.2 только появлялись новые инструкции, в основном нацеленные на специализированные вычисления.
В 2010 появились первые процессоры с поддержкой набора инструкций для аппаратного шифрования AES, этот набор инструкций тоже использует SSE-регистры.
Регистры SSE называются XMM и наличествуют XMM0-XMM7 для 32-битного Protected Mode и дополнительными XMM8-XMM15 для режима 64-битного Long Mode.
Все регистры XMM-регистры 128-битные, но максимальный размер данных, над которым можно совершать операции это FP64-числа. Последнее обусловлено предназначением данного расширения – параллельная обработка данных.
Программное введение:
Когда я только начинал работать с FPU, меня поразила невообразимая сложность работы с ним. Во-первых, из всех 8-ми регистров, прямой доступ был только к двум.
Во-вторых, напрямую загружать данные в них нельзя было, то есть, скажем, инструкция fld 100.0 не поддерживается. А, в-третьих, из регистров общего назначения тоже
нельзя было загрузить данные. Если вторая проблема в SSE не решена, то о первой и третье подобного сказать нельзя.
В данном обзоре рассматриваются только SISD инструкции, призванные заменить FPU аналоги.
Начнем-с. Перво-наперво стоит узнать, как же можно записать данные в xmm-регистр. SSE может работать с FP32 (float) и FP64(double) IEEE754-числами.
Для прямой записи из памяти предусмотрены инструкции MOVSS и MOVSD .
Их мнемоники расшифровываются так:
MOVSS – MOVE SCALAR(Bottom) SINGLE
MOVSD – MOVE SCALAR(Bottom) DOUBLE
Данные инструкции поддерживают только запись вида XMM-to-MEMORY и MEMORY-to-XMM.
Для записи из регистра общего назначения в регистр XMM и обратно есть инструкции MOVD и MOVQ .
Их мнемоники расшифровываются так:
MOVD – MOV DOUBLE WORD(DWORD)
MOVQ – MOV QUAD WORD(QWORD)
Перейдем к основным арифметическим операциям.
Сложение:
ADDSS – ADD SCALAR SINGLE
ADDSD – ADD SCALAR DOUBLE
Вычитание:
SUBSS – SUB SCALAR SINGLE
SUBSD – SUB SCALAR DOUBLE
Умножение:
MULSS – MUL SCALAR SINGLE
MULSD – MUL SCALAR DOUBLE
Деление:
DIVSS – DIV SCALAR SINGLE
DIVSD – DIV SCALAR DOUBLE
Примечание:
XMM-регистры могут быть разделены на два 64-битных FP64 числа или четыре 32-битные FP32 числа.
В данном случае SINGLE и DOUBLE обозначают FP32 и FP64 соответственно. SCALAR – скалярное значение, выраженное одним числом, в отличие от векторного.
В случае работы со скалярными значениями используется нижний SINGLE или DOUBLE(т.е. нижние 32 или 64-бита соответственно) XMM-регистров.
Недостаток SSE заключается в том, что среди инструкций нет тригонометрических функций. Sin Cos Tan Ctan – все эти функции придется реализовать самостоятельно.
Правда, есть бесплатная Intel Aproximated Math Library, скачать ее можно по адресу: www.intel.com/design/pentiumiii/devtools/AMaths.zip.
В связи с данным фактом, в качестве алгоритма для практической реализации был выбран ряд Тейлора для функции синуса. Это ,конечно, не самый быстрый алгоритм,
но, пожалуй, самый простой. Мы будем использовать 8 членов ряда, что предоставит вполне достаточную точность.
В связи со спецификой Protected Mode, а именно, невозможностью прямой передачи 64-битных чисел через стек (нет, конечно, можно, только по частям но неудобно),
рассмотрим еще одну инструкцию, которую мы задействуем в нашей программе.
CVTSS2SD – ConVerT Scalar Single 2(to) Scalar Double
И ее сестра делающая обратное:
СVTSD2SS – ConVerT Scalar Double 2(to) Scalar Single
Данная инструкция принимает два аргумента, в качестве второго аргумента может выступать либо XMM-регистр либо 32-битная ячейка памяти – DWORD.
Примеры использования SSE-комманд:
movss xmm0,dword[myFP32]
movss xmm0,xmm1
movss dword[myFP32],xmm0
movsd xmm0,qword[myFP64]
movsd xmm0,xmm1
movsd qword[myFP64],xmm0
movd xmm0,eax
movd eax,xmm0
add/sub/mul/div:
addss xmm0,dword[myFP32]
subsd xmm0,xmm1
mulss xmm0,dword[myFP32]
divsd xmm0,xmm1
Математическое введение:
В качестве тестового алгоритма мы будем использовать ряд Тейлора для функции синуса. Алгоритм представляет собой простой численный метод.
В нашем случае мы используем 8 членов этого ряда, это не слишком много и вполне достаточно для того, что бы обеспечить довольно точные вычисления.
Во всяком случае, отклонение от fsin(аппаратная реализация Sin – FPU) минимально.
Используемая формула выглядит так:
Программная реализация:
В случае с SSE мы воспользуемся всеми восемью регистрами, а что касается FPU – мы будем использовать только st0 и st1.
Благо использование памяти в качестве буфера оказалось эффективней, чем использование всех регистров FPU, к тому же так проще и удобней.
Вычисление будут проходить так:
Сначала мы вычислим значения всех членов ряда, а потом приступим к их суммированию. Подсчет факториалов проводить не будем, так как это пустая
трата процессорного времени в данном случае.
Программная реализация на SSE:
proc sin_sse angle
;Нам понадобятся два экземпляра аргумента:
cvtss2sd xmm0,[angle] ;; Первый будет выступать как результат возведения в степень.
movq xmm1,xmm0 ;; Второй как множитель, это сделано для того что б минимизировать обращения к памяти.
;; xmm0 = angle.
;; xmm1 = angle. ;; далее x=X=Angle
mulsd xmm0,xmm1 ; Возводим X в третью степень.
mulsd xmm0,xmm1 ;
movq xmm2,xmm0 ; xmm2 = xmm0 = x^3
mulsd xmm0,xmm1 ; Продолжаем возведение.
mulsd xmm0,xmm1 ; Теперь уже в пятую степень.
movq xmm3,xmm0 ; xmm3 = xmm0 = x^5
mulsd xmm0,xmm1
mulsd xmm0,xmm1
movq xmm4,xmm0 ;; xmm4 = xmm0 = x^7
mulsd xmm0,xmm1
mulsd xmm0,xmm1
movq xmm5,xmm0 ;; xmm5 = xmm0 = x^9
mulsd xmm0,xmm1
mulsd xmm0,xmm1
movq xmm6,xmm0 ;; xmm6 = xmm0 = x^11
mulsd xmm0,xmm1
mulsd xmm0,xmm1
movq xmm7,xmm0 ;; xmm7 = xmm0 = x^13
mulsd xmm0,xmm1 ;; Наконец возводим X в 15-ю степень и заканчиваем возведение.
mulsd xmm0,xmm1 ;; xmm0 = x^15
;; Переходим к делению всех промежуточных результатов X ^ n, на n!.
divsd xmm0,[divers_sd+48] ; X^15 div 15!
divsd xmm7,[divers_sd+40] ; X^13 div 13!
divsd xmm6,[divers_sd+32] ; X^11 div 11!
divsd xmm5,[divers_sd+24] ; X^9 div 9!
divsd xmm4,[divers_sd+16] ; X^7 div 7!
divsd xmm3,[divers_sd+8] ; X^5 div 5!
divsd xmm2,[divers_sd] ; X^3 div 3!
subsd xmm1,xmm2 ; x – x^3/3!
addsd xmm1,xmm3 ; + x^5 / 5!
subsd xmm1,xmm4 ; – x^7 / 7!
addsd xmm1,xmm5 ; + x^9 / 9!
subsd xmm1,xmm6 ; – x^11 / 11!
addsd xmm1,xmm7 ; + x^13 / 13!
subsd xmm1,xmm0 ; – x^15 / 15!
;; В EAX результат не поместится
movq [SinsdResult],xmm1
;; Но если нужно добавить функции переносимость, есть два варианта.
cvtsd2ss xmm1,xmm1
mov eax,xmm1
ret
SinsdResult dq 0.0
divers_sd dq 6.0,120.0,5040.0,362880.0,39916800.0,6227020800.0,1307674368000.0
endp
Что касается FPU версии данной функции, то в ней мы поступим несколько иначе. Мы воспользуемся буфером в виде 16*4 байт. В последний QWORD
запишем результат. И в качестве делителя будем использовать память, это не страшно т.к. данные будут расположены на одной и той же странице, а это
значит, что данная страница уже будет прокеширована и обращения к ней будут довольно быстрыми. Суммирование и вычитание членов ряда так же будет
проведено в конце.
Программная реализация на FPU:
proc sin_fpu angle
fld [angle] ; загружаем X. st0=X
fmul [angle]
fmul [angle] ; st0 = X^3
fld st0 ; st1 = st0
fdiv [divers_fpu] ; Делим X^3 на 3! не отходя от кассы
fstp qword[res] ; легким движением стека FPU, st1 превращается в st0
;; qword[res] = x^3 / 3!
fmul [angle]
fmul [angle]
fld st0 ; st0 = st1 = X^5
fdiv [divers_fpu+8]
fstp qword[res+8]
;; qword[res+8] = x^5 / 5!
fmul [angle]
fmul [angle]
fld st0 ; st0 = st1 = X^7
fdiv [divers_fpu+16]
fstp qword[res+16]
;; qword[res+16] = x^7 / 7!
fmul [angle]
fmul [angle]
fld st0 ; st0 = st1 = X^9
fdiv [divers_fpu+24]
fstp qword[res+24]
;; qword[res+24] = x^9 / 9!
fmul [angle]
fmul [angle]
fld st0 ; st0 = st1 = X^11
fdiv [divers_fpu+32]
fstp qword[res+32]
;; qword[res+32] = x^11 / 11!
fmul [angle]
fmul [angle]
fld st0 ; st0 = st1 = X^13
fdiv [divers_fpu+40]
fstp qword[res+40]
;; qword[res+40] = x^13 / 13!
fmul [angle]
fmul [angle] ; st0 = st1 = X^15
fdiv [divers_fpu+48]
fstp qword[res+48]
;; qword[res] = x^15 / 15!
fld [angle] ; st0 = X
fsub qword[res] ; X – x^3/3!
fadd qword[res+8] ; + x^5 / 5!
fsub qword[res+16] ; – x^7 / 7!
fadd qword[res+24] ; + x^9 / 9!
fsub qword[res+32] ; – x^11 / 11!
fadd qword[res+40] ; + x^13 / 13!
fsub qword[res+48] ; – x^15 / 15!
fstp qword[res+56] ; Сохраняем результат вычислений.
ret
res_fpu dq 0.0
res dd 14 dup(0)
divers_fpu dq 6.0,120.0,5040.0,362880.0,39916800.0,6227020800.0,1307674368000.0
endp
Обе функции были протестированы в программе WinTest и вот ее результаты:
sin_FPU – 145-150 тактов в цикле на 1000 итераций и около 1300-1800 при первом вызове при использовании FP64 и 150-165 для FP80.
Такая потеря скорости связана с тем, что при первом вызове память еще не прокеширована.
sin_SSE – около 140-141 тактов в цикле на 1000 итераций, при первом вызове результат аналогичный FPU.
На заметку: так же я тестировал SSE через память (аналогично FPU-алгоритму) и FPU через использование всех регистров, в обоих случаях
имела место серьезная потеря производительности. 220-230 тактов для SSE-версии с использование буферов и около 250-300 для FPU через регистры.
FXCH – оказалась очень медленной инструкцией, а SSE не помогло даже то что страница с данными находилась в кеше.
Примечание:
Основываясь на результатах тестирования, я могу сказать, что разница в результатах может быть лишь погрешностью. Это было проверено опытным путем.
Я несколько раз перезагружал компьютер и в разных случаях выигрывал SSE или FPU. Это дает повод предположить, что имела место немаленькая погрешность
и разница в результатах является ее и только ее порождением. Но Intel Optimization Manual говорит об обратном. По документации разница между SSE и FPU
командами около 1-2 тактов в пользу SSE, т.е. SSE-команды на 1-2 такта в среднем, выполняются быстрее.
Выводы:
Как показала практика, при использовании SSE в качестве FPU мы почти ничего не теряем. Важно то, что такое однопоточное SISD использование не является эффективным.
Всю свою настоящую мощь SSE показывает именно в параллельных вычислениях. Какой смысл считать за N тактов, 1 FP32 сложение/вычитание
или любую другую арифметическую операцию, если можно посчитать за те же N-тактов целых четыре FP32 или 2 FP64. Вопрос остается лишь
в распараллеливании алгоритмов. Стоит ли использовать SSE? Однозначно стоит. Данное расширение присутствует во всех процессорах, начиная с Pentium III и AMD K7.
Важно: Регистры XMM предположительно не сохраняються при переключении задач и точно не сохраняются при использовании API. Тот же GDI+ не восстанавливает их значения.
Nota Bene:
1. Тестирование проводилось на процессоре с не самым старым ядром. Еще при релизе мелькала фраза о масштабных оптимизациях во всех блоках.
При схожей частоте данный процессор в ряде приложений оказывается быстрее, чем, скажем Core 2 на ядре Conroe(первое поколение Core 2).
Это собственно к чему: SSE не всегда было таким быстрым, как и FPU. На разных архитектурах вы увидите как выигрыш от использования SSE
так и серьезный проигрыш.
2. Данный численный метод не является самым быстрым, он даже не распараллелен. Аппаратный FSIN выполняется за 80-100 тактов с FP80/FP64 точностью.
Существуют так же другие численные методы для нахождения тригонометрических и других функций, которые намного эффективней данного и практически позволяют
сделать эти вычисления более быстрыми, нежели FSIN.
Програмно-аппаратная конфигурация:
CPU: Intel Core 2 Duo E8200 2.66 Ghz @ 3.6 Ghz 450.0 FSB * 8.0.
RAM: Corsair XMS2 5-5-5-18 800Mhz @ 900Mhz 5-6-6-21. FSB:MEM = 1:2
MB: Gigabyte GA P35DS3L (BIOS неизвестен – никогда не изменялся.)
GPU: Saphire Radeon HD5870 1GB GPU Clock = 850 Mhz Memory Clock = 4800(1200 Phys).
PSU: Cooler Master Elite 333 Stock PSU 450 Wt.
OS: Windows 7 Ultimate x86
FASM: 1.67 recompiled by MadMatt(Matt Childress).
Ссылки:
http://users.egl.net/talktomatt/default.html
http://programmersforum.ru/showthread.php?t=55270 – тема, где можно найти программу для тестирования времени выполнения.
Автор данной программы некто bogrus. Его профиль есть на форуме WASM.RU но, он неактивен уже 3-й год.
Статья из второго выпуска “журнала ПРОграммистов”.
Скачать этот номер можно по ссылке.
Ознакомиться со всеми номерами журнала.
30th
Апр
Что, где, когда или с чего начать программировать?
Многие хотят стать программистами или же улучшить свои познания в этом увлекательном занятии. Но, как только человек хочет начать, что-то осваивать, перед ним встает вопрос: «…а с чего начать?». Собственно в данной статье, я попытаюсь ответить на этот распространенный вопрос.
Что, где, когда или… с чего начать программировать?
Пискунов Денис
by spamer www.programmersforum.ru
В связи с тем, что в Интернете, да и не только в нем, довольно часто можно встретить людей, которые далеки от программирования, но желают постигнуть его и которые знают некоторые азы сего занятия, но не знают, что им делать дальше, я и пишу данную статью.
«В первый раз – в первый класс»
Для начала, человеку желающему научиться программировать, необходимо скачать/купить книгу по какому-то языку программирования. Я бы советовал для начала скачать электронную книгу, потому-что вдруг вам это занятие не понравится, а деньги на бумажную версию будут уже потрачены. Теперь давайте определимся с языком.
Многие, уже знающие люди, начинают спорить на счет-того, какой язык лучше выбрать начинающим для изучения. Но в нашем случае, я спорить ни с кем не буду, а просто посоветую для начала выбрать язык программирования Pascal. С чем связан такой выбор? Да все очень просто, начинающему будет намного проще понять логику работы программы (алгоритма) в Pascal’е, чем скажем, например в С++ или Assembler.
Так, с языком определились. Теперь вернемся к выбору книги. Как в интернете, так и на прилавках магазинов, лежит огромное количество разнообразной литературы по программированию. А какой-же учебник скачать/купить нам? Скажу сразу, ни в коем случае не покупайте книги типа «Программирование для чайников». Полезного из такой брошюры вы не возьмете ничего, а вот представление о программировании, после ее прочтения, у вас будет неправильное, а то и вообще пугающее. Собственно по Pascal’ю советую следующие материалы и учебники [1-4]. В данной литературе предоставляется хорошее и понятное описание структуры языка, команд, структур данных и т.д. Также присутствуют примеры решения задач и задания для самостоятельного выполнения.
Выбираем среду разработки [5-7]
С языком и обучающим материалом определились. И вот теперь осталось выбрать и установить среду для написания программы или как правильнее – «Интегрированную среду разработки» (IDE, Integrated development environment). Собственно, что представляет собой IDE? Попросту, это набор программных средств, при помощи которых программист разрабатывает программное обеспечение. Так как изучать мы будем чистый Pascal, то и приложения мы будем писать консольные, посему я советую следующую среду разработки – Turbo Pascal 7.0 и кросс-платформенный компилятор FreePascal. Конечно, можно выбрать и что-то современнее, например TurboDelphi, Delphi 2010 или альтернативный Geany. Но для новичка в программировании, я считаю – это будет неправильно, так как в IDE Delphi увидеть логику работы программы, структуру языка и т.д., будет тяжело.
После вот таких приготовлений – садимся читать выбранную книгу, и хочу заметить, не просто читать, а читать, запоминать и разбираться в написанном. Если будете просто читать книжку, то вы потратите свое время в пустую. Поэтому, после получения некоторого теоретического материала, обязательно необходимо все полученные знания закрепить на практике. А точнее – садимся и пишем свою первую программу… Hello World J. Справились с этой задачей, ставим себе новую и реализуем, не знаете, что себе задать – в учебниках есть практические задания. После прочтения книги и при имеющихся знаниях – сделайте свой собственный не большой проект, например «Телефонный справочник», «вариант игрушки» и т.д.
Далее, после того, как вы чувствуете, что довольно хорошо владеете изученным языком, а возможно и уже некой технологией, необходимо решить для себя: «…а нравится-ли мне данная отрасль программирования?». Для ответа на этот вопрос, с помощью любого поисковика ищем информацию о следующих, так сказать, видах программирования:
. системное программирование
. прикладное программирование
. веб – программирование
После прочтения соответствующей информации и при уже имеющихся знаниях в программировании – вы должны выбрать дальнейший вид поля своей деятельности. Если вы определились, тогда начинайте углубленное изучение* выбранного направления.
* Помимо чтения литературы, также желательно общаться на соответствующих форумах. Например, выберите для себя один или два форума и, так сказать – «живите на них». На таких ресурсах Интернета можно довольно много узнать полезной информации, поделиться с кем-то такой-же информацией. Также всегда можно попросить помощи у профессионалов, например, что бы вам объяснили непонятный момент при изучении.
Вот еще такой нюанс – не надо думать, что программирование заключается только в знании языков программирования. Если вы хотите стать действительно хорошим программистом, то вам обязательно нужно знать дополнительные технологии. Например, можно полностью посвятить себя изучению программирования графики, попутно ознакомиться с разнообразными графическими библиотеками, алгоритмами, связанными с графикой и т.д. Следовательно, для достижения каких-либо целей, вам всегда необходимо читать соответствующую литературу, а также запомнить один из основных моментов – научиться пользоваться поиском. Так как большинство вопросов уже обсуждалось в Интернете, то правильный запрос в поисковую систему даст вам интересующий ответ.
Заключение
И не бойтесь спрашивать знающих людей о том, что не знаете сами – ничего предосудительного в этом нет. В общем, не надо ждать доброго дяденьку, который придет, все Вам разжует и в рот положит, а начинайте сами достигать поставленной цели. Так что, дерзайте.
Ресурсы
. Т.А. Павловская. Паскаль. Программирование на языке высокого уровня: практикум. – С.Петербург, Питер
-Юг, 2003
. Валерий Попов. Паскаль и Дельфи. Самоучитель. – С.Петербург, Питер, 2003
. В.В. Фаронов. Turbo Pascal 7.0. Начальный курс: учебное пособие. – М., КноРус, 2007
. А.Я. Архангельский. Язык Pascal и основы программирования в Delphi. – М., Бином-Пресс, 2008
. Скачать компилятор FreePascal http://www.freepascal.org/download.var
. Скачать компилятор Geany http://download.geany.org
. Скачать компиляторы DELPHI http://delphilab.ru
Статья из второго выпуска “журнала ПРОграммистов”.
Скачать этот номер можно по ссылке.
Ознакомиться со всеми номерами журнала.
Обсудить на форуме — Что, где, когда или… с чего начать программировать?
30th
Изменения в языке Дельфи 2010
Задача предназначена для представления краткого обзора нововведений в язык Дельфи (2010) по сравнению с Дельфи 7.
Изменения в языке Дельфи 2010
Utkin
Благодаря активным попыткам компании Embecadero влиять на рынок продуктов разработки программ язык Дельфи быстро развивается, однако это развитие направлено в основном на попытки наверстать все нововведения в современных языках программирования (таких как С#). Никаких принципиально новых разработок и концепций не внедряется.
Директива Inline (появилась в Дельфи 2005)
По аналогии с С++, функции и процедуры теперь могут быть встраиваемыми со всеми вытекающими последствиями. А именно использование данной директивы не гарантирует вставку тела функции вместо ее вызова. Кроме того, существует еще целый ряд ограничений (согласно справочной системе). Эта директива бесполезна:
. при позднем связывании (virtual, dynamic, message);
. для функций и процедур имеющих ассемблерные вставки;
. для конструкторов и деструкторов, там она работать не будет (о чем Вам компилятор обязательно пожалуется);
. для главного блока программы, секций инициализации и финализации модулей;
. если метод класса обращается к членам класса с более низкой видимостью, чем сам метод. Например, если public метод обращается к private методу, то для такого метода inline-подстановка осуществляться не будет;
. для процедур и функций, которые используются в выражениях проверки условия циклов while и repeat.
Как сделать процедуру встроенной?
Procedure Add (var x: Integer; y: Integer); Inline;
Регулировать поведение inline можно следующими директивами:
{$INLINE ON} – по умолчанию включена, разрешает работу Inline;
{$INLINE AUTO} – будет осуществлена попытка встраивание кода функций и процедур, если:
а) они помечены как Inline;
б) если их размер будет менее 32-х байт.
{$INLINE OFF} – не разрешает работу Inline.
Следует отметить, что и в классическом С++ Inline никогда не была высокоэффективным механизмом, а учитывая ограничения, накладываемые компилятором Дельфи, ее использование под большим вопросом.
Перегрузка операторов (появилась в Delphi.Net)
В отличие от С++ перегрузка осуществляется немного по-другому. Для перегрузки операторов перегружается не символ оператора, а его символическое обозначение (сигнатура). Перегружать можно только для операций с экземплярами классов.
Нужно обратить внимание – TRUNC, ROUND, INC, DEC считаются операторами, а не процедурами и функциями.
Вот пример использования:
TMyClass = class
class operator Add(a, b: TMyClass): TMyClass; // Перегрузка сложение для TMyClass
class operator Subtract(a, b: TMyClass): TMyclass; // Вычитание для TMyClass
class operator Implicit(a: Integer): TMyClass; // Неявное преобразование Integer в TMyClass
class operator Implicit(a: TMyClass): Integer; // Неявное преобразование TMyClass в Integer
class operator Explicit(a: Double): TMyClass; // Явное преобразование Double в TMyClass
end;
// Пример описание сигнатуры Add для перегрузки сложения для типа TMyClass
TMyClass.Add(a, b: TMyClass): TMyClass;
begin
…
end;
var
x, y: TMyClassbegin
x := 12; // Неявное преобразование из Integer
y := x + x; // Вызов TMyClass.Add(a, b: TMyClass): TMyClass
b := b + 100; // Вызов TMyClass.Add(b, TMyClass.Implicit(100))
end;
Подробней о перегрузке операторов можно почитать здесь: http://www.realcoding.net/articles/delphinet-peregruzka-operatorov.html
Помощники класса (Class Helpers)
Интересный механизм (ответ Дельфи на расширители классов в С#), призванный решить некоторые проблемы в обход наследования. Служит для дополнения класса новыми методами и свойствами.
type
TMyClass = class
procedure MyProc;
function MyFunc: Integer;
end;
…
procedure TMyClass.MyProc;
var
X: Integer;
begin
X := MyFunc;
end;
function TMyClass.MyFunc: Integer;
begin
…
end;
…
type
TMyClassHelper = class helper for TMyClass
procedure HelloWorld;
function MyFunc: Integer;
end;
…
procedure TMyClassHelper.HelloWorld;
begin
WriteLn(Self.ClassName); // Здесь будет возвращен тип TMyClass, а не TMyClassHelper
end;
function TMyClassHelper.MyFunc: Integer;
begin
…
end;
…
var
X: TMyClass;
Begin
X := TMyClass.Create;
X.MyProc; // Вызов TMyClass.MyProc
X.HelloWorld; // Вызов TMyClassHelper.HelloWorld
X.MyFunc; // Вызов TMyClassHelper.MyFunc
end;
По сути, вариация на тему множественного наследования, но есть одна особенность – помощники класса позволяют дополнять любой существующий класс, без создания нового. Обратите внимание, что механизм помощника класса не использует явного упоминания Self при обращении к полям класса (помогаемого класса). То есть, HelloWorld имеет право обращаться к полям TMyClass (просто в нашем примере их нет). Аналогично TMyClass также имеет доступ к полям TMyClassHelper (в случае, если класс и его помощник объявлены в одном модуле).
С практической точки зрения удобный механизм, кроме одной детали – класс должен иметь только одного помощника, имеет ли он помощника проверить во время выполнения программы нельзя. Если в классе имеется несколько помощников (неважно в каком юните, лишь бы он видел класс), считаться рабочим будет только самый последний из объявленных. Это значит, что если TMyClass уже имел помощника, то будут доступны методы именно TMyClassHelper, поскольку именно он объявлен последним. Таким образом, в лучшем случае, два и более помощника для одного класса вызовут ошибку компиляции, в худшем трудно отлавливаемую ошибку, жалобы программиста на багги в IDE и компиляторе и много потерянного времени. Чем сложней проект, тем трудней будет установить причину ошибки.
С теоретической точки зрения механизм противоречивый – он увеличивает сцепляемость объектов и юнитов между собой. Перед использованием помощника, я должен проверить все модули, из которых доступен данный класс на предмет проверки существования такого помощника (представьте большой проект). Это нарушает принципы инкапсуляции – если раньше перед использованием класса нужно было знать только его интерфейс, то теперь для использования помощников, я должен отслеживать существование класса во всех модулях, где имеется доступ к данному классу. С этого момента механизм интерфейсов уже не играет особой роли, поскольку, обращаясь к объекту какого-либо класса, всегда можно обнаружить такой букет неизвестных методов, что интерфейсная часть класса становится даже вредной. Это нарушает принцип сокрытия данных – благодаря помощникам я могу менять работу своих и чужих классов и могу иметь доступ к его полям (в рамках юнита). Кстати, это ограничение на доступ к полям в рамках юнита также сводит на нет многие его плюсы – проще вписать новые методы в сам класс (или наследовать новый), чем создавать путаницу в классе, юните и проекте.
Записи стали объектами
И теперь имеют свои методы, свойства и конструкторы.
type
TMyRecord = record
type
TInnerColorType = Integer;
var
Red: Integer;
class var
Blue: Integer;
procedure printRed();
constructor Create(val: Integer);
property RedProperty: TInnerColorType read Red write Red;
class property BlueProp: TInnerColorType read Blue write Blue;
end;
constructor TMyRecord.Create(val: Integer);
begin
Red := val;
end;
procedure TMyRecord.printRed;
begin
writeln(’Red: ‘, Red);
end;
Но, сокращенная запись по-прежнему разрешена (поэтому старые проекты должны переноситься и с сокращенной формой записей).
Абстрактные классы
type
TAbstractClass = class abstract
procedure SomeProcedure;
end;
Разрешены полностью абстрактные классы (раньше допускались только конкретные методы), содержащие объявления методов для дальнейшего их перекрытия в потомках.
strict private и strict protected
Строгое private – метод или свойство для класса и невидимое никому, вне класса даже в рамках текущего юнита.
Строгое protected – методы в этой секции будут видимы самому классу и его наследникам.
Таким образом, полное объявление выглядит теперь так
type
TKlass = class(TForm)
strict protected
protected
strict private
private
public
published
automated
end;
Не наследуемые классы
По аналогии с С#, в Дельфи 2010 существуют классы от которых дальнейшее наследование невозможно:
type
TAbstractClass = class sealed
procedure SomeProcedure;
end;
Весьма сомнительное удовольствие для рядового разработчика. Никаких реальных преимуществ такой класс не дает. Точные причины создания такого механизма не известны и преимущества от его использования очень призрачны – наследование не разрушает класса предка, поэтому и запечатывать их особой необходимости нет. Считается, что запечатанные классы работают быстрей обычных (сам не проверял) и они применяются для .NET платформы (сугубо в утилитарных целях – не все обертки над низкоуровневыми операциями, такими как WinApi, можно сделать наследуемыми).
Классовые константы (возникло в Delphi
Классы могут иметь константы – сами классы, а не порождаемые от них объекты.
type
TClassWithConstant = class
public
const SomeConst = ‘This is a class constant’;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
ShowMessage(TClassWithConstant.SomeConst);
end;
Классовые типы (возникло в Delphi
Класс может теперь содержать описание типа, которое можно использовать только в пределах данного класса.
type
TClassWithClassType = class
private
type
TRecordWithinAClass = record
SomeField: string;
end;
public
class var
RecordWithinAClass: TRecordWithinAClass;
end;
…
procedure TForm1.FormCreate(Sender: TObject);
begin
TClassWithClassType.RecordWithinAClass.SomeField := ‘This is a field of a class type declaration’;
ShowMessage(TClassWithClassType.RecordWithinAClass.SomeField);
end;
Еще одно сомнительное удовольствие. Описание типа это не конкретная структура, зачем прятать его описание в тело класса?
Классовые переменные (возникло в Delphi
Класс может содержать переменные по аналогии с константами:
Type
X = class (TObject)
Public
Var
Y: Integer;
End;
Пожалуй, единственное, где это может пригодиться это работа с RTTI, вообще классы в Дельфи стали больше напоминать юниты – такие вот юниты внутри юнитов. Обратите внимание, что переменные класса могут находиться в любой секции (секции в данном случае влияют на область видимости данных переменных), тогда как поля класса не могут быть public (в Дельфи 7 могли). Применение статических полей в классе делает Дельфи все более ориентированным в сторону С# (и менее в сторону Паскаля).
Вложенные классы
Теперь классы можно объявлять внутри классов, цель избежать конфликта имен, локализовать все связанные классы между собой:
type
TOuterClass = class
strict private
MyField: Integer;
public
type
TInnerClass = class
public
MyInnerField: Integer;
procedure InnerProc;
end;
procedure OuterProc;
end;
procedure TOuterClass.TInnerClass.InnerProc;
begin
…
end;
Классы все больше перенимают концепцию модулей. Понятно, что данное нововведение дань .Net, но реальной пользы от него опять же не очень много – раньше конфликта имен избегали префиксами A и F, не могу сказать, что новый механизм дал программистам новые возможности. Также как и попытка использовать вложенные классы для складывания всего в одну большую кучу наряду с помощниками классов сильно напоминают лебедь, рак и щуку, растаскивающие Дельфи в разные стороны.
Финальные методы класса
В классах можно создавать виртуальные методы, которые перекрыть нельзя:
TAbstractClass = class abstract
public
procedure Bar; virtual;
end;
TFinalMethodClass = class(TAbstractClass)
public
procedure Bar; override; final;
end;
Переопределить «Bar» уже больше нельзя.
Статические методы класса
У классов могут быть статические методы класса – то есть методы, которые можно вызвать от типа класса. Такие методы не имеют доступа к полям класса (также как и не могут получить Self на конкретный экземпляр данного класса):
type
TMyClass = class
strict private
class var
FX: Integer;
strict protected
// Note: accessors for class properties must be declared class static.
class function GetX: Integer; static;
class procedure SetX(val: Integer); static;
public
class property X: Integer read GetX write SetX;
class procedure StatProc(s: String); static;
end;
TMyClass.X := 17;
TMyClass.StatProc(’Hello’);
Здесь же представлен пример организации свойств классов. Их использование полностью аналогично использованию переменных и констант класса.
for-element-in-collection
Теперь компилятор способен распознавать итерации в контейнерах:
for Element in ArrayExpr do Stmt;
for Element in StringExpr do Stmt;
for Element in SetExpr do Stmt;
for Element in CollectionExpr do Stmt;
Вот развернутый пример:
var
A: Array [1..6] of String;
I: String;
….
for i in A do
begin
Memo1.Lines.Add(i);
end;
Обратите внимание, что I имеет тип String это не индекс массива, а конкретные значения, которые будут получаться из массива. Кое-где конечно автоматизирует, но представьте, что мне нужно написать некий метод, в котором происходит копирование одного массива в другой. Использовать все равно придется стандартный цикл for, либо писать еще один метод – добавление элемента в массив.
Динамическая инициализация массивов
Теперь массивы получили свои конструкторы:
Type
TMas = Array of String;
Var
Mas: TMas;
Mas := TMas.Create(‘Hello’, ’World’, ’!’);
Я, конечно, не против «Create» как конструктора по умолчанию, но уже сейчас из-за отсутствия внятной русскоязычной литературы по данной теме встречаются статьи, в которых авторитетные господа пишут, что конструктор обязательно должен называться Create (речь идет не только о массивах, но также о записях и конструкторах класса). Так вот конструктор должен называться Create только для массивов. Для всех остальных имя конструктора не обязательно должно быть Create (но желательно, особенно для классов).
Дженерики
Шаблоны, они и в С++ шаблоны. Считается что первые шаблоны возникли в С++, но вообще-то они пришли из функционального программирования и правильное их название параметрический полиморфизм. Явление, когда компилятор сам вырабатывает соответствующие варианты кода на основании обобщенного алгоритма:
TList<T> = class
private
FItems: array of T;
FCount: Integer;
procedure Grow(ACapacity: Integer);
function GetItem(AIndex: Integer): T;
procedure SetItem(AIndex: Integer; AValue: T);
public
procedure Add(const AItem: T);
procedure AddRange(const AItems: array of T);
procedure RemoveAt(AIndex: Integer);
procedure Clear;
property Item[AIndex: Integer]: T read GetItem write SetItem; default;
property Count: Integer read FCount;
end;
Вот пример списка содержащего произвольные (но однотипные элементы). Тип элементов определяется на момент объявления переменной:
ilist: TList<Integer>;
То есть мы создали список целых чисел (а можно, к примеру, список строк). Дженерики удобно использовать применительно к алгоритмам контейнеров данных и комбинаторным алгоритмам. Конкретные реализации алгоритмов можно посмотреть в модуле Generics.Collections, где есть TArray, TList, TStack, TQueue, TDictionary, TObjectList, TObjectQueue, TObjectStack, TObjectDictionary и TEnumerator, способные работать с разными типами данных.
Также необходимо отметить особенность дженериков (и шаблонов в С++) – обобщенные алгоритмы экономят время программиста, сокращая только его код, но для каждого типа (для каждой комбинации типов) всегда генерируется новая версия алгоритма (поэтому размер скомпилированных программ увеличивается).
Заключение
Большинство механизмов представленных здесь:
. обеспечивают совместимость с .NET
. дань моде
. попытка угнаться за Microsoft Visual Studio
Язык не содержит принципиальных отличий и мощных механизмов, которые действительно были бы востребованы именно программистами на языке Дельфи. Все нововведения навязаны, искусственны и не всегда соответствуют концепциям ООП. Большое количество противоречивых инструментов может только запутать программистов и в течение ближайших лет можно ожидать некоторого количества критических статей в адрес языка программирования Дельфи.
Комментарий автора
Личные впечатления о среде сложились следующие: сплошные недоделки (да и в 2009-м не лучше), ждать следующую версию наверно не стоит. FrameWork идет в комплекте, ничего доустанавливать не надо. Несмотря на заявленные требования не ниже 1 гигабайта ОЗУ, у меня и при 512-ти с тормозами, но работает.
Ресурсы
. Хроники «айтишника» http://skiminog.livejournal.com/33610.html
. Общество разработчиков Embecadero http://edn.embarcadero.com
. Углубленный материал по перегрузке операторов в Дельфи http://www.realcoding.net/articles/delphinet-peregruzka-operatorov.html.
. Онлайн-перевод англоязычных материалов статьи http://www.translate.ru
Статья из второго выпуска “журнала ПРОграммистов”.
Скачать этот номер можно по ссылке.
Ознакомиться со всеми номерами журнала.
Облако меток
css реестр ассемблер timer SaveToFile ShellExecute программы массив советы word MySQL SQL ListView pos random компоненты дата LoadFromFile form база данных сеть html php RichEdit indy строки Win Api tstringlist Image мысли макросы Edit ListBox office C/C++ memo графика StringGrid canvas поиск файл Pascal форма Файлы интернет Microsoft Office Excel excel winapi журнал ПРОграммист DelphiКупить рекламу на сайте за 1000 руб
пишите сюда - alarforum@yandex.ru
Да и по любым другим вопросам пишите на почту
пеллетные котлы
Пеллетный котел Emtas
Наши форумы по программированию:
- Форум Web программирование (веб)
- Delphi форумы
- Форумы C (Си)
- Форум .NET Frameworks (точка нет фреймворки)
- Форум Java (джава)
- Форум низкоуровневое программирование
- Форум VBA (вба)
- Форум OpenGL
- Форум DirectX
- Форум CAD проектирование
- Форум по операционным системам
- Форум Software (Софт)
- Форум Hardware (Компьютерное железо)