Излучение Хокинга — главный аргумент учёных относительно распада (испарения) небольших чёрных дыр, которые теоретически могут возникнуть в ходе экспериментов на БАК. На этом эффекте основана идея сингулярного реактора — устройства для получения энергии из чёрной дыры за счёт излучения Хокинга.
Релятивистские струи, джеты — это струи плазмы, вырывающиеся из центров (ядер) таких астрономических объектов, как активные галактики, квазары и радиогалактики. Обычно у объекта наблюдается две струи...
Чрезвычайно редкая и мощная форма молнии в верхних слоях атмосферы, которая простирается от вершины грозы до нижних уровней ионосферы
В отличие от более известных спрайтов, которые также представляют собой электрические разряды в верхних слоях атмосферы, гигантские струи более мощные и достигают более высоких уровней в атмосфере
Синие джеты возникают у вершин мощных кучево-дождевых облаков. Они представляют собой светящиеся объекты конической формы, распространяющиеся до высот 40-50 км. Общая протяженность джета достигает 20-30 км, а ширина в верхней части до нескольких километров. Имеет, синее свечение, что отражено в названии.
Синий джет - вид мимолетных светящихся явлений. Представляют собой вспышку, возникающую в результате электрического разряда в атмосфере над грозовым облаком. Длина синего джета может составлять 50-70 километров.
Международная команда исследователей, включающая ученых ФИАН и МФТИ провела анализ радионаблюдений высокого разрешения джета галактики М87 — выбрасываемой струи плазмы из окрестности массивной черной дыры. Ученые определили богатую внутреннюю структуру джета и визуально показали, что в ней преобладают три спиральные нити, которые, вероятно, возникают в результате неустойчивости Кельвина-Гельмгольца, развивающейся в сверхзвуковом потоке.
Работа опубликована в журнале Monthly Notices of the Royal Astronomical Society. Исследование ученых позволяет глубже понять процесс влияния сверхмассивных черных дыр на джеты. Наблюдения проводились в течение трех дней в мае 2009 года с использованием радиоинтерферометра VLBA, состоящего из десяти 25-метровых антенн, удаленных друг от друга на максимальное расстояние 8611 километров и одной 25-метровой антенны радиоинтерферометра VLA, а также 100-метрового телескопа Эффельсберг. Полученные изображения имеют динамический диапазон, который позволил зарегистрировать более слабое излучение джета с помощью многочастотных наблюдений с высоким разрешением.
M87 — сверхгигантская эллиптическая галактика в созвездии Девы, имеющая в центре сверхмассивную черную дыру, которая в 6,5 миллиард раз массивнее Солнца. Падение на нее вещества галактики делает ее ядро активным и мощным источником излучения, сопровождающееся выделением большого количества энергии. Часть окружающего падающего на черную дыру вещества выбрасывается из нее и порождает джеты, которые простираются на 4900 световых лет. Одновременно рождается два биполярных, противонаправленных джета, формирующихся вдоль оси вращения сверхмассивной черной дыры. Но за счет того, что ученые видят активную галактику наклоненной под малым углом к лучу зрения, излучение от джета, направленное от земли, значительно подавляется. В итоге видимым остается только один джет. Однако на изображениях некоторых активных галактик контр-джет возможно наблюдать, в том числе в М87.
«Мы взяли для анализа уникальные наблюдения, проведенные в течение трех дней еще в 2009 году, но только сейчас доступные расчетные мощности позволили их обработать. Наблюдения проводились самыми мощными телескопами и за счет очень высокого динамического диапазона мы получили четкое радиоизображении струи джета и увидели минимально слабый поток в деталях. В целом галактика M87 одна из самых наблюдаемых объектов в Космосе из-за относительной близости к нам и сверхмассивной черной дыры в ее центре. Наблюдения за ней позволило изучить один из самых загадочных объектов во Вселенной, и это не только тень от черной дыры, но и струю джета, на которую мы смотрим чуть-чуть сбоку, под углом 18 градусов. Сам джет хорошо светит в радиодиапазоне за счет течения плазмы по магнитным силовым линиям со скоростью, близкой к скорости света, что позволяет нам строить его наглядные радиоизображения», — комментирует Евгения Кравченко, старший научный сотрудник лаборатории физики высоких энергий МФТИ.
Полученные изображения открыли перед учеными неоднородную структуру струи джета на масштабах, где происходит его активная коллимация и ускорение. Она напоминает твидовый узор в виде сплетенной косы спиральных волокон. Их моделирование продемонстрировало, что закручивание центральных волокон вызвано нестабильностями Кельвина-Гельмгольца, развивающимися в плазменной струе. Похожий спиральный узор наблюдался в некоторых внегалактических струях и раньше, но в намного больших масштабах, где влияние на плазму струи сверхмассивной черной дыры и магнитного поля значительно слабее. В этом исследовании астрофизики показывают, что такая структура волокон может быть обусловлена физическими процессами в непосредственной близости от черной дыры.
Изображения струи в М87 в полной интенсивности на частотах 8 и 15 ГГц
«На струю джета сильное влияние оказывает не только сама черная дыра, но и аккреционный диск. Скорость падения вещества галактики на чёрную дыру порядка скорости света, и из-за большого углового момента вещество не может напрямую падать в дыру, а образует при падении плоский диск. Мы можем наблюдать подобную картину, если направим сильную струю воды из крана в раковину. Около слива она растекается и образует спираль и только затем вытекает. Здесь так же — в диске создается спиральное вращение вещества. За счет закручивания материи черной дыры и аккреционного диска, закручивается и магнитное поле, которое приобретает спиралевидную форму. Небольшой наклон вращения аккреционного диска относительно оси вращения сверхмассивной черной дыры может оказывать влияние на джет, например вызывать его прецессию и генерировать развитие в нем плазменных неоднородностей, которые мы наблюдаем в струе в М87», — заключила Евгения Кравченко.
Также неоднородна и яркость джета в радиогалактике М87. Снимки демонстрируют уярчение излучения к краю струи. Такое поведение можно интерпретировать либо разной скоростью, плотностью и энергией потока плазмы в центре и по краям, либо крупномасштабным спиральным магнитным полем. Все эти вопросы, в том числе о составе струи и механизмов генерации излучения в ней пока остаются открытыми, до конца причина разной яркости потока не выяснены.
На этом необычном фото телескопа «Хаббл» можно увидеть нечто, напоминающее след от «выстрела», пронзившего газопылевые облака.
Но на самом деле космическая обсерватория запечатлела достаточно редкий феномен, известный под названием объект Хербига-Аро. Объекты Хербига-Аро являются продуктом деятельности новорожденных звезд, окруженных протопланетными дисками.
При некоторых обстоятельствах, их взаимодействие с веществом из диска может приводить к образованию джетов, состоящих из ионизированного газа. Говоря проще, новорожденное светило начинает выбрасывать материю в окружающее пространство.
Выброшенное звездой вещество движется со значительной скоростью, которая может достигать нескольких сотен км/с. Его взаимодействие с газопылевыми облаками приводит к образованию характерных ударных волн, которые и называются объектами Хербига-Аро. Их протяженность может достигать десяти световых лет. Также стоит сказать, что объекты Хербига-Аро являются весьма краткосрочным по астрономическим меркам явлением. Всего через несколько десятков тысяч лет они полностью рассеиваются в межзвездном пространстве.
В 2020 году исследователи зафиксировали быстрый радиовсплеск от нейтронной звезды SGR 1935+2154 с мощным магнитным полем. В 2022-м радиовсплеск от того же объекта повторился, а скорость вращения звезды стала стремительно меняться.
SGR 1935+2154
Вращающиеся нейтронные звезды с мощным магнитным полем называют магнитарами. Магнитное поле такой звезды сильнее 100 триллионов гауссов, что в 100 триллионов раз больше магнитного поля Земли. Первый магнитар SGR 1900+14 был зафиксирован в созвездии Орла в 1998 году.
Нейтронные звезды способны излучать в радио– и рентгеновском диапазоне. Излучение исходит от полюсов магнитаров, а земные приборы регистрируют сигнал за счет постоянного вращения космического объекта. Скорость вращения магнитаров огромна, что требует колоссальной энергии для ее поддержания. В итоге частота вращения нейтронных звезд со временем уменьшается из-за потери энергии.
Но иногда, по пока неясным науке причинам, с такими объектами происходят глитчи. Глитчи — это неожиданное увеличение частоты вращения и излучения магнитаров и пульсаров. Два глитча, о которых рассказали авторы новой работы, произошли в 2022 году с магнитаром SGR 1935+2154 и стали одними из сильнейших за всю историю астрономических наблюдений. Между глитчами вращение звезды резко уменьшалось, а частота излучения в рентгеновском диапазоне снижалась.
С помощью канадского интерферометрического телескопа (CHIME), радиотелескопа Грин-Бэнк, рентгеновской обсерватории на борту Международной космической станции (NICER) и космического телескопа жесткого рентгеновского диапазона (nuSTAR) исследователи получили массив данных о скорости вращения SGR 1935+2154 и всплесках излучения.
Ученые считают, что стремительное уменьшение скорости вращения SGR 1935+2154 между глитчами происходило из-за магнитосферных вихрей. Магнитосферный ветер — экзотическое явление для Солнечной системы. Оно вносит изменения в геометрию магнитного поля, создавая момент вращения. В 2020 году такой вихрь длился 10 часов в пиковой фазе.
По мнению исследователей, быстрое замедление вращения, вызванное магнитосферными вихрями, после первого глитча привело к последующему увеличению вращения и ко второму глитчу. Дело в том, что глитч приводит к резкой корректировке вращения всей звезды. Внутри магнитара меняется движение сверхтекучей среды относительно остальной части звезды.
«Значительная часть ядра и большинство свободных нейтронов во внутренней коре находятся в сверхтекучем состоянии», — заявили ученые.
По их мнению, несколько десятков процентов магнитара существуют в сверхтекучем состоянии.
Также оказалось, что рентгеновское излучение и радиовплески происходят в разное время. Радиовплески появляются во время уменьшения излучения в рентгеновском диапазоне. Авторы выдвинули гипотезу, по которой быстрые радиовсплески возникают из-за резкого уменьшения и последующего увеличения скорости вращения магнитара. Ускорение компенсирует притормаживание вращения, вызванное сильными магнитосферными ветрами.
В свою очередь, активная фаза рентгеновского излучения длилась около двух дней и происходила в среднем один раз в минуту. Впоследствии частота достигла пика — четыре всплеска в минуту, а затем снизилась менее чем до 10 процентов по сравнению со временем до возникновения глитча.
Исследователи предположили, что за мягкое рентгеновское излучение (с длиной волны более 0,1-0,2 нанометра) в основном ответственно тепловое излучение, исходящее с поверхности магнитара, а жесткое рентгеновское излучение (длина волны менее 0,1-0,2 нанометра) исходит от магнитосферы нейтронной звезды.
Ученые планируют продолжить изучение магнитаров, но теперь сконцентрироваться на динамике излучения плазменных ветров, которые играют роль в потере массы звезды.
Гигантский джет — чрезвычайно редкая и мощная форма молнии в верхних слоях атмосферы, которая простирается от вершины грозы до нижних уровней ионосферы. В отличие от спрайтов, которые также представляют собой электрические разряды в верхних слоях атмосферы, гигантские струи более мощные
Внимание! Фото информационное! Таблички используются как наглядное пособие!
Не все так гладко. Привезли 47 штук из 70. Остальные обещали довести как разберут вещи во второй машине. Почти все разобрали. Отправлять буду с завтрашнего дня. Думается дня за три управлюсь. Как связаться, да все просто - пишите вк https://vk.com/hrustalev_e или телегу @hru5talev . Размер 25х25 см. Часть забронировали музеи.
Всех обнял. С уважением, Евгений Хрусталев.
Кто не в курсе - рекомендую почитать мои предыдущие посты.
Это та самая галактика, в центре которой находится сверхмассивная чёрная дыра, чью тень мы увидели в 2019 году. На этом изображении видна почти вся галактика целиком, в виде туманного жёлтого пятна, а также достаточно хорошо виден джет, создаваемый чёрной дырой в центре галактики. Длина этого джета из плазмы — 3000 световых лет.
Сверхмассивная чёрная дыра в центре галактики М 87. Это самое первое в истории человечества качественное изображение тени чёрной дыры, полученное напрямую в радиодиапазоне.
Было построено и облетано два прототипа, но в производство не пошел.
Mr Ian Chichester-Miles,бывший главный инженер испытатель авиационного подразделения British Aerospace в Hatfield, Hertfordshire,основал Chichester-Miles Consultants (CMC) для разработки высокопроизводительного легкого бизнес джета. Разработка четырехместного Leopard началась в январе 1981 и проходила достаточно интенсивно,макет должен был быть завершен в начале 1982 года. Детальная разработка и строительство прототипа компанией Designability Ltd из Dilton Marsh, Wiltshire, началась в июле 1982, по контракту с CMC.Продемонстрированный на авиационной выставке в 1983 г. натурный макет этого четырехместного самолета и его проектные летные характеристики привлекли большое внимание специалистов и вероятных покупателей. Конструктор, ставший и главой созданной им самостоятельной авиафирмы, заявил журналистам, что в 1984г. начнутся летные испытания «Леопарда», а с конца 1985г. его можно будет покупать, и назвал цену машины — 400 тыс. долларов. Однако эту программу осуществить не удалось. Отработка новых легких реактивных двигателей для «Леопарда» затянулась. Первый опытный экземпляр машины лишь в ноябре 1988 г.получило от управления гражданской авиации Англии согласие на его испытания в воздухе. Первый полет этот прототип (G-BKRL) совершил 12 декабря I988 года на RAE Bedford под управлением летчика испытателя Cranfield: Angus McVitie. Первый прототип оснащался двумя адаптированными двигателями Noel Penny Turbines NPT301. CMC ограничил скорость прототипа 130kt и макс высоту 3,500ft. Испытания показали,что прототип Leopard мог лететь на скоростях меньше 75kt с закрылками на 41°.С убранными закрылками скорость сваливания была меньше 90kt.
Одна из главных особенностей «Леопарда» в том, что его среднерасположенное стреловидное крыло (размах — 7,16 м, площадь — 5,85 кв. м, угол стреловидности — 25°) изготовлено полностью из стеклопластика. Профили — суперкритические типа АРА. Закрылки с электроприводом размещены по всему размаху. Они могут отклоняться до - 45(в каждую сторону) , что обеспечивает высокие взлетно-посадочные характеристики и возможность использования их в качестве воздушных тормозов. Элеронов на крыле нет.
Фюзеляж, длиной более 7 м, как и крыло, изготовлен из стеклопластика. За четырехместной кабиной, закрытой фонарем, который открывается вверх с помощью электропривода, находится небольшой грузовой отсек, а под полом — топливные баки емкостью 590 л.Масса пустого незаправленного самолёта всего 830 кг. Фюзеляж делится на три части: передняя носовая с оборудованием,негерметичная,далее герметичная кабина экипажа и третья часть негерметичная для багажа...Герметизация от bleed air. Макс разница давления в кабине: 0.65 bar (9.6 psi)
Оригинально выполнено оперение самолета. Все его поверхности — цельноповоротные. Консоли стабилизатора(тейлероны) могут отклоняться как одновременно, так и дифференцированно, обеспечивая в этом случае управление по крену. Взлетный вес опытного образца — 1160 кг.На серийном самолете предполагалось сделать жидкостную антиобледенительную систему.
Силовая установка «Леопарда». Два ее легких турбореактивных двигателя NPT-301 тягой по 145 кг должны были быть размещены в гондолах по бокам хвостовой части. На серийных машинах предполагается использовать двигатели NPT-754 тягой по 340 кг. Это позволило бы увеличить взлетный вес самолета более чем на 600 кг. Новый ТРД должен был быть готов в начале осени 1989 г. Но на нашем втором прототипе стоят Williams FJX-1 из-за проблем с разработкой движков под него.
Этот самолет второй прототип Leopard, оснащен двигателями Williams FJX-1. Он совершил свой первый полет в апреле 1997, и выставлялся на Ошкоше в 1999 году.Он был герметичным в отличии от первого прототипа. Chichester Miles остановил разработку этого самолета,ожидая доступности Williams FJ-22.
Места для пассажиров,поразительно,какие огромные окна у этого самолета если учесть,что он герметичен.
Цена на сентябрь 1998 года составляла $1,350,000.Кроме основной гражданской, прорабатывалась и военная модель "Леопарда" - лёгкий патрульно-ударный и учебно-тренировочный самолёт с тандемной кабиной и пилонами для подвески оружия и топливных баков.
ЛТХ(второй прототип) Экипаж: один пилот Вместимость: 3 пассажира Длина: 24 ft 9 in (7.54 m) Размах крыла: 23 ft 6 in (7.16 m) Высота: 6 ft 9 in (2.06 m) Площадь крыла: 63 ft2 (5.85 m2) Двигатель: 2 × Williams FJX-1, 700 lbf (3.1 kN) каждый Макс скорость: 540 mph (869 km/h) Дальность: 1,726 miles (2,778 km) Высота: 55,000 ft (16,765 m) Скороподъемность: 6,340 ft/min (32.7 m/s)
но поскольку прототипы различались,потому как есть еще такие данные: Размах крыла: 25 ft 0 in in 7.62 m Длина: 25 ft 10 in in 7.88 m Площадь крыла: 64.5 sqft 6.00 m2 Макс взлетный вес: 4000 lb 1814 kg Вес пустого: 2200 lb 998 kg Макс нагрузка на крыло: 62 lb/sq ft 302 kg/m2 Двигателей: 2 Взлетная тяга: 700 lb 3.1 kN Крейсер: M 0.76 810 km/h Крейсерская высота: 45,000 ft 13.700 m Дальность (с полной нагрузкой) 1500 nm 2775 km Взлетная дистанция: 2750 ft 838 m Посадочная дистанция: 2450 ft 747 m
Кентавры — группа астероидов, вращающихся вокруг Солнца между орбитами Юпитера и Нептуна. Но, в отличии от комет, они достаточно холодны для того, чтобы лед переходил в газообразное состояние, что является одной из причин появления хвоста кометы.
Наблюдая за Кентавром 29P, который некоторые астрономы иногда и относят к кометам, телескоп обнаружил три необычных джета. Исследования показали, что они вызваны моноксидом углерода (CO) и углекислым газом (CO₂), причем второй был обнаружен впервые на этом объекте. Результат моделирования показал, что джеты исходят из разных областей ядра кентавра, а их направление указывает на то, что ядро может представлять собой совокупность отдельных объектов с различным составом. Однако пока нельзя исключать и другие сценарии.
"Тот факт, что 29P имеет такие резкие различия в содержании СО и СО₂ на своей поверхности, говорит о том, что объект может состоять из нескольких частей. Возможно, два объекта в прошлом слились и образовали этот кентавр, который представляет собой смесь очень разных тел, прошедших разные пути формирования. Это бросает вызов нашим представлениям о том, как создаются и эволюционируют первые объекты в поясе Койпера", — заявил Джеронимо Виллануэва, соавтор исследования.
Не рекомендуется принимать данную статью близко к сердцу или как истину в последней инстанции. Скорее как научпоп-грелку для мозгов. Тут довольно много упрощений, упущений или просто странно написанных моментов. Если что-то объяснено совсем криво, то добро пожаловать в комментарии.
Все сказанное далее применимо везде, но детали описаны для защищенного режима x86 (только я опустил префиксEдля регистров) и языков семейства C (в основном C и C++, местами C#). Для понимания рекомендуется знать про указатели и базово представлять, что происходит в процессоре.
Приятного чтения.
Думаю все помнят, что такое функция в математике
Начнем со сложного
Глоссарий
Чувствую, что большинство не имеет ни малейшего представления об указателях
В части про x86 я упоминал о том, что процессор представляет из себя бешенный самоуправляемый калькулятор, которому указом может быть разве что хранилище кода, аппаратный сброс и немаскируемое прерывание. И что у него есть системная шина, состоящая из шины адреса, шины данных и шины управления, к которой подключена память, хранящая данные и код.
А память это огромная одномерная полоска, в которой каждая ячейка имеет свой порядковый номер от 0 до 2^(разрядность шины адреса)-1. И указатель представляет из себя самую обычную численную переменную, хранящую номер (адрес) любой ячейки.
Я не понимаю, почему для многих это настолько сложная тема, а существенное количество новичков забрасывают изучение C после встречи с ними. Они в своей сути максимально элементарны (до тех пор, пока не нужно исправлять уязвимости и ошибки, которые появились в результате злоупотребления или неполного понимания нюансов).
Также существуют ссылки, так или иначе представляющие из себя подвид указателей, но обычно с запретом на арифметику с ними (основная сложность и опасность указателей) и/или подсчетом количества активных ссылок для сборки мусора.
Все языки или имеют указатели (Pascal, C, C++, блоки unsafe в C# и Rust), или являются ссылочными (Java, C#, Python, JS, т.д. (почти все современные языки)). С++ тоже имеет ссылки, но как синтаксический сахар над указателями, призванный упростить их передачу в функции и избавиться от проблемы нулевых указателей.
Ассемблер - если вы даже примерно не знаете, что это такое, то первую часть статьи вероятно можно пролистать и перейти на обсуждение парадигм.
Регистр - именованная численная переменная внутри процессора. Может иметь особое предназначение, а может просто использоваться для хранения любых чисел и арифметики. Их немного.
Инструкция - одиночная команда, представляет из себя последовательность из нескольких байт со специальным значением. Любая программа представляет из себя последовательность инструкций внутри памяти. Указатель на инструкцию, которая будет выполнена следующей, содержится в регистре IP, который сам увеличивается после выполнения каждой инструкции. По-хорошему стоило нарисовать пошаговую наглядную анимацию с демонстрацией всех регистров и куском ассемблерного кода, но мне лень, а в гугле я ничего толкового не нашел.
Метка - константа, содержащая адрес чего-либо. К примеру процедуры или глобальной переменной. Примерно как метка для goto в высокоуровневых языках, но универсальнее. Применяются при написании на ассемблере, в процессоре как таковые не существуют и разрушаются до обычных чисел при ассемблировании и линковке.
Разыменование - операция над указателем, когда тот превращается в переменную с адресом, который был записан в указателе (своеобразный пульт Д/У для переменной).
Парадигма - "стиль" написания и постройки архитектуры программы, частично определяется языком. Технически на том же C можно писать в практически любой парадигме (через костыли можно писать в стиле ООП, через макросы реализовать метапрограммирование, а через нестандартные расширения вообще ядерный бред), но родная для него - процедурная. А Go, к примеру, хоть и имеет недоклассы и методы, но лишен практически всех благ ООП и не сильно далеко ушел от C.
Синтаксический сахар - необязательная возможность, которая сокращает количество кода, повышает его читаемость или удобство поддержки
Стек
Большинство процессоров Фон-Неймановской архитектуры в своей конструкции предлагают механизмы стека. Кто играл в покер должны вспомнить стеки фишек. То есть некие значения, сложенные друг на друга (обычно это переменные, в частности адреса в памяти). При этом основными операциями являются добавление фишки на вершину (инструкция PUSH) и ее снятие (инструкция POP). Еще можно косвенно читать и заменять (перезаписывать) фишки относительно вершины (на вершину указывает регистр SP, неявно обновляется через POP и PUSH) или основания (указывает регистр BP) вглубь.
Стеки можно переключать (но не в MOS6502), это нужно для многозадачности
Помимо хранения локальных переменных стек позволяет делать довольно интересную вещь: мы можем положить на стек все необходимые аргументы (x в математике, но их может быть несколько), адрес следующей инструкции (взяв из указателя инструкции IP, это будет адрес возврата), после чего совершить прыжок на какой-нибудь другой адрес (сохранение адреса и прыжок делаются инструкцией CALL).
А на этом адресе может быть функция. Сначала она кладет текущий BP на стек, после чего приравнивает основание к вершине (BP к SP), тем самым создав для себя "новый стек" сразу после предыдущего (это еще называется стековым кадром), в котором якобы лежит только значение старого основания стека (BP), чтобы можно было восстановить его перед возвратом.
Серое это стековый кадр от предыдущей функции
После чего функция может прочитать переданные ей аргументы относительно основания своего стека вниз (технически это будет выход за границы текущего стека), выполнить с этим какие-либо действия (записать в файл, вывести в консоль, просто перемножить) и сохранить результат (обычно результат сохраняется не на стек, а в регистр AX).
После чего функция восстанавливает старое значение BP, сняв его со стека, и выполняет команду RET, которая снимает со стека адрес возврата и совершает переход на него, тем самым переключившись на инструкцию сразу после CALL.
И эта система так или иначе перекочевала в большинство высокоуровневых языков начиная с FORTRAN. К примеру в C CALL превратился в круглые скобки, RET в return, а адреса и метки в имена (при этом без скобок они все еще являются указателями, то есть адресами).
Пример кода (к сожалению, штатного форматирования не предусмотрено):
#include <stdio.h> int pow2(int x) { return x * x; } int main() { int y = pow2(16); // Вызов функции. В y будет сохранено число 256 printf("%p", pow2); // Без скобок вместо вызова просто выведет адрес функции return 0; // Возврат нуля из главной функции означает отсутствие ошибок. }
Такой стиль программирования называется процедурным (выделение кода в блоки называется структурным). А вот называть процедурный язык функциональным совершенно неправильно, ибо функциональное программирование ≠ процедурное, они даже в разных категориях (императивное и декларативное).
А теперь скомпилируем этот код и разберем ассемблерный листинг
Важно понимать, что стек в большинстве архитектур традиционно растет от больших адресов к меньшим (и в x86). То есть для того, чтобы отодвинуть его вершину вверх, от регистра SP нужно отнимать значения, а вот для ужимания и съедания ненужных значений к указателю на вершину значения прибавляют. И для доступа к значениям относительно основания или вершины это тоже важно учитывать. Это может звучать запутанно, но через время привыкаешь и всё становится очевидным.
Надеюсь это возможно будет разобрать. Код скомпилирован MSVC v19.28 для x86 со стандартными настройками в Godbolt
Post scriptum
Это всё довольно упрощенно. Как минимум, в защищенном режиме используется больше 5 разных соглашений о вызове, которые отличаются деталями реализации. Это было описание для cdecl, обычно используемого в C. Еще часто используются соглашения pascal, fastcall, thiscall, winapi и другие. Fastcall, к примеру, избегает хранения аргументов на стеке, если их возможно передать через регистры, что улучшает производительность. А winapi отличается от cdecl тем, что функция сама очищает стек от аргументов для себя при возврате. А еще я упустил, к примеру, сохранение регистров, которые функция может перезаписать и испортить, а потому обязана предварительно сохранить и перед возвратом восстановить, передачу переменного количества аргументов (как в printf) и возврат значений шире 32 бит (которые не влезут в EAX).
Плюс сейчас мало кто компилирует ПО под защищенный 32-битный режим, а в длинном режиме (AMD64) используется пара других соглашений, основанных на fastcall и имеющих несколько отличий друг от друга.
Так процедура или функция? Или подпрограмма?
Процедурное программирование предлагает делить код на подпрограммы, которые принято называть функциями и процедурами (функция обычно является наиболее понятным, частым и обобщенным названием, поэтому я его использую).
Процедура от функции отличается только тем, что функция возвращает какое-то значение (как в математике), а вот процедура этого не делает. Не во всех языках явно есть процедуры (в Pascal есть, но не в C). В таком случае их заменяют функции, возвращающие ничего (void, Unit, undefined, None).
Хотя и тут есть свои особенности. К примеру функция, возвращающая void в C и Java является прямым аналогом процедур, как-либо использовать возвращенное значение из такой функции невозможно, ибо его нет физически. А вот Unit в Kotlin это синглтон (а-ля единственная и уникальная константа уникального типа), ссылку на который можно присвоить в переменную, но в этом особого смысла нет. Undefined в JS и None в Python тоже уникальные константы специальных типов.
Но не тут-то было
Вроде бы процедура никогда не может ничего вернуть...
Только она этого никогда не делает напрямую. При она этом может записать результат в глобальную переменную, а еще часто принимает в себя указатели или ссылки, по которым может записать результат. Это еще удобно тем, что можно "вернуть" несколько значений. Пример:
void procedure(int x1, int *x2, int *x3) { // Функция ничего не возвращает, то есть это процедура *x2 = x1 * x1; // Разыменовываем указатель и записываем по его адресу результат. *x3 = x1 * x1 * x1; // Разыменовываем другой указатель и записываем по его адресу результат. } int y1, y2; procedure(16, &y1, &y2); // В y1 оказался результат, аналогичный прошлому примеру. А в y2 куб числа.
PS: оператор звездочка при указании типа превращает его в тип-указатель, а при применении на переменную-указатель разыменовывает ее до изначальной переменной. Амперсанд превращает переменную в указатель на нее (иногда еще называется оператором получения адреса).
То есть мы вернули сразу 2 разных значения из процедуры, которая якобы ничего не возвращает. Чудеса. Подобные чудеса есть в том числе в Pascal с явным делением на процедуры и функции (плюс там это сделано немного удобнее). Хотя механизм тут отличается от того, который используется в возврате значения из функции и совпадает с механизмом передачи обычных аргументов, поэтому никакой магии.
Еще про связь с математикой
Главное отличие функций в программировании от функций в математике в том, что они могут делать что-то на стороне и не обязаны возвращать одинаковый результат при одинаковых аргументах.
К примеру функция получения случайного числа по определению не может существовать в математике, если она не принимает в себя предыдущее случайное число или зерно для его видоизменения. Или функция записи в файл, возвращающая 0 в случае успеха и другое число при провале. Ко всему прочему, такая функция имеет побочный нематематический эффект, то есть запись в файл, что тоже недопустимо традиционной математикой без высоких абстракций.
Поэтому придумали чистые функции. По сути это ограничитель, которые делают функцию полным отражением таковой в математике. Им запрещено возвращать разные значения при одинаковых аргументах (точнее запрещено всё, что может такое позволить сделать), запрещено обращаться к нечистым функциям, запрещено обращаться к тому, что не является аргументом или локальной переменной, запрещены вообще любые действия, которые могут сделать что-то на стороне (даже функция sin() в C не всегда является чистой, ибо может зависеть от состояния FPU).
Чистые функции через ключевое слово pure явно есть в D и FORTRAN (проверка на чистоту во время компиляции), а также являются основой функционального программирования.
Чистая процедура тоже имеет право на жизнь, используя механизм со ссылками (на счет указателей не уверен из-за возможности арифметики над ними).
Функциональное программирование
Это очень сложная категория, которую постоянно путают с процедурным программированием. А еще это де-факто противоположный стиль: декларативный. Традиционное императивное программирование детально описывает процесс получения результата, а декларативное сам результат, без деталей реализации (хотя разделение обычно довольно нечеткое). При этом второй типичен для языков разметки типа HTML и CSS. То есть, условно, как одна и та же операция могла бы выглядеть в императивном и декларативном стиле:
document.tags.A.color = "blue" /* Императивный (JSSS). Сделать ссылки синими */
a { color: blue } /* Декларативный (CSS). Ссылки должны быть синими */
Почувствуйте разницу.
И функциональное программирование я никогда не изучал и слишком мало о нем знаю. Так что готовьтесь к ошибкам и не воспринимайте всё за чистую монету.
Внутри чистого функционального программирования
Основано полностью на математике, все функции обязаны быть чистыми. Операция присваивания запрещена (разрешены константы), переменных в привычном виде нет. Прикольно? Очень!
Во многих процедурных языках функции и процедуры являются объектами второго класса (не путать с классами из ООП), что не позволяет их свободно присваивать в переменные, передавать как аргументы в другие функции или возвращать из них (только через указатели). Функциональные языки расценивают функцию как объект первого класса, то есть их можно, а часто нужно передавать в другие функции напрямую.
Это дает некоторые преимущества, особенно в плане безопасности и при работе с многопоточностью (по причине неизменяемости данных и отсутствия глобального состояния), но вся концепция имеет один фатальный недостаток: вы мало чего полезного можете сделать, ибо что ввод, что вывод являются математически нечистыми, а потому запрещены. Вот такое вот гениальное изобретение безумных математиков.
Функциональное и процедурное программирование. Холст, масло
Каждый чисто функциональный язык выкручивается из этого по-своему, к примеру через монады. Это позволяет им существовать вне шуток и даже использоваться на практике.
Это не все особенности функциональных языков, но одни из самых важных. Самый известный такой язык: Haskell. Функциональные F#, Lisp, ML и многие другие не являются 100% чистыми.
Смешанное функциональное программирование
Последнее время часто используются смешанные языки, к примеру вместе с процедурным или объектно-ориентированным программированием, что избавляет от ограничений математики, но дает гибкость в том, что функциями можно оперировать как с любыми другими типами данных, а еще дает много очень удобного сахара вроде замыканий и лямбд. Это C#, Python, JS, частично Java и C++ (в них нужны костыли в виде интерфейсов из одного метода или оберток над указателями).
Особенности
Функции как объект первого класса. К примеру в C# это реализовано через систему делегатов, которые представляют из себя тип-обертку для функций:
Action<string> printer = Console.WriteLine; // Action<string> - тип-делегат. Неявно создаем его объект и присваиваем туда функцию printer("Hello, World!"); // Вызываем функцию через делегат
Локальные функции: как обычные, только вложенные в другую функцию (объявленные внутри нее)
void Func1() { // Глобальная функция void Func2() { // Локальная функция Console.WriteLine("В локальной функции"); } Func2(); }
Лямбды: возможность объявить безымянную функцию посреди кода (часто удобнее локальных)
var pow2AsLambda = x => x * x; // => - оператор лямбда-выражения pow2AsLambda(5); // Вернет 25
Замыкания (можно использовать вместе с лямбдами и локальными функциями):
int someValue = 42; var pow2AsLambda = x => x * x + someValue; // someValue будет захвачено замыканием, хотя напрямую не передано pow2AsLambda(5); // Вернет 67
Особенность замыканий в том, что они могут захватить локальную переменную родительской функции внутрь себя, продлевая ей время жизни за пределы блока с кодом. После чего такую лямбду можно передать в другую функцию, которая просто так не имеет доступа к someValue (в обычных условиях someValue вообще уже будет уничтожен), а вот переданная лямбда сможет ее прочитать или записать всегда и откуда угодно. И самое интересное то, что значение этой переменной будет сохранятся между вызовами к замыканию. То есть она становится глобальной, но видимой только из функции-замыкания.
ООП
Посмотрим на индекс TIOBE по популярности языков. Фиолетовым я пометил чистые функциональные языки. Языки, имеющие возможности, присущие функциональным языкам (смешанные) - синим, процедурным - красным, а объектно-ориентированным (ООП) - зеленым. Языки, в которых нельзя объявить функцию, принадлежащую самой себе или модулю, процедурными считать не совсем корректно (Java).
Не претендую на 100% точность
Видите фиолетовые точки? И я не вижу. А вот синих 12. В то же время красных и зеленых по 16. При этом реально применяемый чисто процедурный язык всего 1: С. Остальные имеют какую-либо встроенную поддержку других парадигм.
И сейчас практически всё большое ПО пишут в ООП. Ибо позволяет хоть как-то сдерживать структурированность кода после перехода с десятков тысяч строк к сотням (и миллионам).
Внутри
ООП является развитием идеи процедурного программирования. Проблема была в том, что помимо кода в программе существуют еще и данные, которые было бы неплохо связать с соответствующим им кодом. И через некоторое время после появления процедур появились структуры (они же записи в Pascal, не путать со структурным программированием).
Структуры были удобным способом объединить несколько переменных в единое целое. Пример:
typedef struct user_struct { char *username; int userid; int reputation; } user_t; user_t someuser = { "IvanKr08", 17002, 253 }; // Инициализируем значениями someuser.reputation += 10; // Обращаемся к полю
(Вместе с этим объявляем структуру новым типом через typedef, что не делается по умолчанию в C, в отличии от C++. Один из ярких примеров несовместимости C и C++):
После чего user_t превращается в новый тип, как int или char. Можно создать полноценную переменную типа user_t (или массив такого типа), а потом обратиться к какой-нибудь ее части (полю) через оператор точки (.). Можно сделать функцию, которая будет принимать или возвращать переменную типа user_t (к примеру someuser). Можно сделать указатель на user_t, тогда для обращение к полю используется оператор стрелки (->). Или можно встроить переменную типа одной структуры в другую, это тоже не запрещено. Внутри структурная переменная представляет из себя последовательно слепленные поля в одно целое.
Если поля разного размера, то часто добавляют пустоты между полей в целях выравнивания по размеру наибольшего поля и улучшения производительности, но это сложная тема
Возвращаемся к ООП
Суть ООП в том, что помимо полей структуры могут содержать процедуры и функции. Такая структура называется классом, переменная класса - объект, а процедура класса - метод.
Метод принципиально ничем не отличается от любой процедуры или функции, кроме того, что неявно принимает в себя специальный аргумент this (в Python это делается явно, в некоторых языках вместо this может быть self). Он содержит в себе ссылку/указатель на объект класса, от которого был вызван. Пример:
class User { public: char *username; int userid; int reputation;
void print() { printf("Пользователь \"%s\" (%i). Репутация: %i\n", this->username, this->userid, this->reputation); // Постоянно писать this-> не обязательно. Если локальной переменной с таким именем нет, то будет произведен доступ к полю } }; User someuser = { "IvanKr08", 17002, 253 }; // Инициализируем значениями someuser.print(); // Вызвали метод, print() неявно получил в себя указатель this, который указывает на someuser someuser.reputation += 10;// Обращаемся к полю someuser.print(); // Теперь вывод поменяется
Да, никто не мешает объявить обычную функцию, которая будет явно принимать в себя указатель на User и ничего не поменяется, и так повсеместно делают в C (тот же WinAPI на этом построен целиком и полностью), но ООП на самом деле крайне сложная, большая и холиварная тема, которая развивалась на протяжении 60 лет в разных направлениях и которую поднимать тут глупо. Только опишу еще несколько разновидностей методов:
Статичный метод - как обычный метод, только не получает в себя this и вызывается от имени класса, а не объекта (не someuser.method(), а User::method()). Зачем это нужно и в чем отличие от просто функции? Статичный метод можно спрятать за инкапсуляцией, плюс он привязан к классу, а не болтается в глобальном пространстве имен.
Конструктор - автоматически вызывается при создании нового объекта. Может иметь аргументы, тогда обязан явно вызываться как функция с именем класса, т.е. User("IvanKr08", 17002, 253)
Деструктор - есть в C++. В C# называется финализатором и имеет несколько важных отличий. Автоматически вызывается перед уничтожением объекта (к примеру выход из области видимости, явное удаление оператором delete или удаление сборщиком мусора в C#). Не может иметь аргументов.
Свойство - есть в Delphi и C#, куда перешел от первого. С точки зрения синтаксиса это поле, которое можно читать и записывать, только вместо прямого обращения в память вызываются соответствующие методы (set и get). Пример: ... int SomeProperty{ get =>42; set => Console.WriteLine("Set"); } ... test.SomeProperty = 10; // Значение никуда не сохранится, будет выведено "Set" в консоль Console.WriteLine(test.SomeProperty); // Всегда будет 42
Перегрузка оператора - специальный метод, который позволяет переопределить стандартные операции для типа. К примеру нельзя сложить два объекта User или User и число, но если перегрузить оператор +, то можно будет определить свою логику для этого (не обязательно, чтобы оно что-то реально складывало. Это может быть любое действие).
Индексатор - синоним перегрузки оператора квадратных скобок. Объект начинает вести себя как массив.
Функтор - перегрузка оператора круглых скобок (иначе называется перегрузкой оператора вызова функции). Позволяет "вызывать" объект так же, как и функцию. Звучит запутанно, но можно погуглить. Есть в C++. Также бывает функтор в функциональном программировании, но он не имеет ничего общего с функтором в C++.
Виртуальная функция (метод) - самый интересный и сложный тип, составляет основу полиморфизма. Вкратце объяснить его невозможно, но одним из ключевых принципов ООП является наследование, то есть один класс (наследник) копирует в себя все поля и методы другого класса (родитель), и может добавить новые. И суть в том, что указатель (ссылка) на объект класса-наследника может быть присвоен в переменную-указатель родительского типа. А самое интересное то, что любые обращения (к примеру вызовы методов), сделанные через родительский указатель, будут вести себя аналогично обращениям, сделанным напрямую через указатель с типом наследника. Это называется полиформизмом. Но это было бы не слишком полезным, если бы не возможность объявить метод в родительском классе виртуальным, а в наследнике полностью переопределить его код.
И то, что я забыл
Послесловие
Если вы осилили эти 3000 слов и даже смогли что-то понять, то найдите и распечатайте себе утешительную грамоту. А я устал и пойду искать смысл жизни...
UPD: исправлены косяки форматирования, очепятки, добавлено еще пару пунктов про разновидности методов и функциональное программирование.
Комментарии