@@tilir я тоже из неё) это бесплатная школа без преподавателей, куда можно прийти с нуля, в начале идёт достаточно много программирования на СИ, потом с++, потом более широкоиспользуемые языки. Особенность обучения, что нет преподавателей, только задания и куча людей, которые тоже эти задания делают, сдачи друг другу по чек листам, автоматические проверки в начале обучения, кодревью друг друга начиная с плюсовых проектах. Основное обучение достаточно длительное, 2-3 года
Спасибо за лекцию. 59:05 "Что вам мешает привести указатель на ваш объект к char* и поправить там все что угодно инкапсулированное". Мне кажется на практике нам мешает не только совесть, но и формальное UB с точки зрения стандарта. А именно, насколько понимаю, по стандарту мы можем использовать арифметику указателей только в пределах одного массива (и одного значения за его пределами), причем тип указателя должен совпадать с типом массива. Иначе - UB. В дрфате стандарта C++20 есть по этому поводу 7.6.6 [expr.add]: (4) When an expression J that has integral type is added to or subtracted from an expression P of pointer type, the result has the type of P. (4.1) - If P evaluates to a null pointer value and J evaluates to 0, the result is a null pointer value. (4.2) - Otherwise, if P points to an array element i of an array object x with n elements (9.3.4.5), the expressions P + J and J + P (where J has the value j) point to the (possibly-hypothetical) array element i + j of x if 0 ≤ i + j ≤ n and the expression P - J points to the (possibly-hypothetical) array element i − j of x if 0 ≤ i − j ≤ n. (4.3) - Otherwise, the behavior is undefined. ... (6) For addition or subtraction, if the expressions P or Q have type “pointer to cv T”, where T and the array element type are not similar (7.3.6), the behavior is undefined. [Note 2 : In particular, a pointer to a base class cannot be used for pointer arithmetic when the array contains objects of a derived class type. - end note] В данном случае мы попытаемся применить арифметику указателей к объекту, не являющемуся массивом, что в соответсвии с пп (4.2, 4.3) является UB. Не уверен что при желании это нельзя обойти, но кажется наивный подход порочен и по формальному слову стандарта, а не только по совести. Хотя, конечно, на практике это сработает как ожидается) UPD: Глянул на следущий слайд. Насколько понял судя по 6.8.1 [basic.types.general] значение для trivially copyable сначала можно скопировать в массив, а потом уже по нему идти, тогда это уже не будет UB с формальной точки зрения. (2) For any object (other than a potentially-overlapping subobject) of trivially copyable type T, whether or not the object holds a valid value of type T, the underlying bytes (6.7.1) making up the object can be copied into an array of char, unsigned char, or std::byte (17.2.1) If the content of that array is copied back into the object, the object shall subsequently hold its original value. Но в любом случае, конечно не важно, можно ли это делать или нет, мы и правда не хотим так делать) UPD2: На cppreference в статье Pointer declaration первый пример иллюстрирует эту проблему. Там структура из двух интов, но даже без всяких кастов к char* мы не можем без UB добраться от указателя на первый инт ко второму. И хотя мы можем инкрементировать один раз указатель на первый инт (в этом случае указатель ведет себя как указатель на массив из одного элемента, про это есть в сноске в [expr.add] 4.2), то разыменовать и обратиться к этому полю, это уже UB, даже если его тип и значение совпадают с другим указателем, который был получен честным взятием адреса второго поля.
Спасибо за хорошую подборку литературы. Пишу на С++ 5 лет, но все равно интересно Вас слушать. P.S. 😂 Как всегда "залипла" в 4 утра , это действительно интересно. Про буддийских монахов и рис в конце отдельное спасибо. P.P.S. Gilbert Strang ua-cam.com/play/PLE7DDD91010BC51F8.html&feature=shared
p[2] vs 2[p] дело в том что [] может быть перегружен для типа и p[2] может означать что угодно, 2[p] == *(p+2) :) например const num = sizeof(p)/sizeof(0[p]) :). Ой забыл отметить, прекрасные лекции - получаю редкое удовольствие от прослушивания, даже пива не хочется
Константин, здравствуйте. Ребята неоднократно рекомендовали посмотреть ваши лекции. Выглядит все солидно, но есть один вопрос по поводу решения задачи, а именно когда вы говорите, "Синус дешевая операция, sqrt немножко подороже, но тоже довольно дёшево". Что вы имеете ввиду, и по сравнению с чем дешево? На сколько я знаю, тригонометрические функции, да и тоже деление - это +-самые медленные операции с точки зрения процессора. Тоже самое сложение работает быстрее в десятки раз. Если я не прав, то поправьте. Спасибо, и хотелось бы когда-нибудь поработать с таким спецом как вы.
Вы неправы. Вы транслируете чьи-то заблуждения без ссылки на источник. Я очень хочу когда-нибудь записать свои лекции для первого курса по C и ассемблеру, где я разбираю эти вопросы более подробно. Если кратко: в современных CPU просто нет такого понятия как цена инструкции. Любая инструкция на ALU, независимо от того целочисленная она или плавающая не стоит нисколько по сравнению с задержками на промахи кешей и миспредикты бранчей. Исключения остались где-то в области embedded, но они, в общем, исчезающие. Слишком длинный конвейер, слишком широкое окно планировщика. В качестве упражнения вы всегда можете показать класс. Возьмите выложенный мной код и покажите как вы его ускорите за счёт лучшей арифметики.
@@tilir да, стоило прикрепить источник изначально, но не смог вспомнить где именно это видел. Теперь нашёл. Это PBRT, physically based rendering, Third edition, наверняка слышали про ресурс. Прикрепляю цитату: «On current CPU architectures, the slowest mathematical operations are divides, square roots, and trigonometric functions.» Авторы с NVIDIA, Google и иным аналогичным опытом. Я не оспариваю ваши слова, а пытаюсь понять что имеете ввиду вы, и они. Mike Acton так же делал отсылку к этой теме в одной из своих презентаций, по типу: Sqrt это около 200-300 циклов. Могу так же скинуть сорс, если необходимо. Спасибо
quick-bench.com/q/BpfMi_9RSiIbi8uEMq_NVQA81q8 Вот ваши "200--300 циклов": 40 процентов на сферическом бенчмарке в вакууме. Если вы посмотрите на дизассемблер, там затраты скорее на загрузку и выгрузку значений, чем на арифметику. В реальном приложении не будет и сорока процентов. Как говорил Линус Торвальдс, "talk is cheap, show me the code". Исходники для треугольников выложены. Берите и обгоняйте. В процессе многое поймёте.
@@tilir дело не во мне, мне до вашего и их уровня ещё стоит поковыряться с cs, а в том, что то, что говорите вы, идёт вопреки с мнением людей вашего уровня. Мне было интересно узнать что имели вы под, « дешевые инструкции», и они, « это самые медленные инструкции». Но вектор вы задали, поэтому разберусь когда руки дойдут. ps: я доверяю людям, у которых опыта на гору больше, чем у меня, и просто стараюсь найти какую-то истину.
@@wndtn я же написал. Я имею в виду дешевизну по сравнению с промахом мимо L1 или с миспредиктом перехода. Грубо говоря между лишним бранчом и лишним квадратным корнем, я сделаю корень, не особо раздумывая.
Могу предложить забавное использование 2[p]: в шаблоне можем так "задетектить", что переданный объект является указателем или массивом, т.к. для перегруженных операторов такое не сработает.
В лекции есть один запутывающий момент: архитектура с шириной char в один бит не является соответствующей стандарту c++, который требует минимум 8 бит.
Константин, приветствую! Спасибо за крутые лекции )) Вот ухо моё зацепилось, хочу немного придраться 14:18 , 17:19 - "статический массив" - тогда уж "статический или автоматический массив"
Здесь речь идёт о статическом / динамическом массиве в плане известности его размера на этапе компиляции (т.е. статически). Но я согласен, надо было предупредить.
Забавно. Со времен учебника по Borland C++ всегда думал, что ссылки - это операция взятие адреса объекта (захват) по которому компилятору запрещена адресная арифметика (а null - отсутствие объекта). Сейчас на java, интересно полный флеш-бек сделать.
12:56 по поводу 2[p] фантазирую, что это может быть связано с AT&T синтаксисом как-нибудь, там ведь тоже 2(%rax) это "прочитай из памяти по адресу равному значение в rax + 2".
Константин, добрый день. На 5:50 вы говорите о RAM модели, что С++ работает в такой модели, и что объекты не отделены друг от друга. Приведу некоторые ссылки на стандарт. Согласно [intro.memory], "The memory available to a C++ program consists of one or MORE sequences of contiguous bytes."; согласно [intro.object], объекты занимают region of storage, которые имеют storage duration, и (за некоторыми исключениями) уникальные адреса, такие что объекты не пересекаются, а по достижению конца storage duration все указатели на любую часть этого региона инвалидируются; согласно [basic.compound], reinterpret_cast даёт новое значение указателя лишь для pointer-interconvertible типов, а иначе полученный указатель не будет указывать на новый объект (возможно необходим launder); согласно [ptr.launder], чтобы провести указатель через оптимизационный барьер, все байты хранилища, достижимые через новый указатель, также должны быть достижимы через старый указатель; согласно [expr.add], арифметика указателей определена лишь в пределах массивов, и для этих целей одиночные объекты считаются элементами гипотетического одноэлементного массива, а получение указателя вне массива приводит к UB. Учитывая всё это, мне кажется, что ваше утверждение неверно, и на самом деле (боюсь здесь формулировать идею) абстрактная машина работает с объектами, за которыми закрепляется область хранения (память), нежели с памятью, то есть всё таки абстракция суть (изолированные?) объекты, а адреса нужны для их различия (?). Можете прокомментировать? Благодарю.
Любой правильно выделенный объект занимает "region of storage". При этом стандарт молчалив о том что же это за storage, но ясно, что, в любой системе с виртуальной памятью, имеется в виду виртуальная память. Тот факт что этот storage с необходимостью линейный следует из того что внутри любого правильно выделенного региона вы можете линейно (назад и вперёд) ходить с помощью char*. Разумеется при доступе по неправильно выделенном адресу (сформированному вами случайно например), доступе за границы массива и т.п., вы получаете UB Методически правильный способ думать о памяти в рамках базового курса по C++ это думать о линейной памяти с некоторыми ограничениями. А не думать об абстрактной машине, состоящей из каких-то изолированных островков (которые внезапно оказываются вовсе не изолированы, а лежат во вполне конкретных местах виртуальной памяти и т.д.). Второй способ правильный по стандарту, но учить так -- это было бы издевательство над второкурсниками.
34:31 возможна небольшая неточность: this может быть nullptr. Пример Rect* a = nullptr; a->foo(); и если foo() не обращается к полям, это еще и будет работать
То что вы написали это тривиальное UB, работать это "будет" до первого ветерка как и любое другое UB. [expr.ref]: The expression E1->E2 is converted to the equivalent form (*(E1)).E2; the remainder of [expr.ref] will address only the first option (dot).
Спасибо. На слайде 17 (13:43) в pb должно быть значение 01101, а не 11001. Ну и если душнить по максимуму, то не будет ли в такой модели проблем от того, что здесь значение по нулевому смещению? Если мы например попробуем вызвать *&pb приведет к разыменованию нулевого указателя, хотя объект валидный. Может лучше сдвинуть pb немного от начала? Заодно на слайде будет видно, что он задает смещение от начала адресного пространства, а не от себя (хотя это и так было видно на предыдущих слайдах).
Спасибо, отличное наблюдение. Я бы не характеризовал это словом "душнить". Нормальная педантичность. А вы можете предположить чему в этой целевой архитектуре может быть равен NULL чтобы мы не теряли первый бит? =)
@@tilir С NULL хитро придумано) Если оно не обязано быть 0, то можно любое значение из 23-31, которые помещаются в 5 бит, но не входят в доступный для адресации диапазон. Но наверное правильнее (хотя и менее удобно для слайда) просто доступную память мапить не с нуля, а первые несколько бит оставить зарезервированными под nullptr. UPD: под само nullptr разумеется достаточно одного бита, но может быть полезно зарезервезировать рядом несколько невалидных адресов, чтобы заодно отслеживать не только разыменование непосредственно по nullptr, но и в случае если в результате невалидной адресной арифметики адресуем какие-то близкие адреса. На реальной машине как понимаю такого эффекта добиваются бесплатно, просто резервируя первую страницу памяти, так что любое обращение по первым 4кб, а не только по nullptr вызывают segmentation fault.
Хочешь оценить качество курса/книги по плюсам посмотри как объясняют указатели. Указатели - первая тема после обзорной лекции! ЙЕЕЕЕ! Указатель - это расстояние от начала памяти - ВАУ!! (реально первый человек в рунете от кого я услышал именно эту формулировку). И дальше пошло жонглирование кодом.... Возможно поэтому на 2300 просмотров всего 130 лайков. Очень грустно что чтобы понять БАЗОВЫЙ курс по плюсам нужно виртуозно владеть Си. И ещё грустнее что этого курса нет на ютупчике. Надеюсь в следующем релизе будет больше примеров из реального кода. А на дворе шел 2022й и рота красноармейцев
Я с вами совершенно согласен. Мой магистерский курс не воспринимается без базового, а базовый сложно воспринимается без моего курса по C для первокурсников. Но именно мой курс по C наименее подходит для выкладки: там трёхчасовые семинары и очень мало экшена, люди пишут код, я правлю какие-то ошибки, что-то объясняю, потом они опять пишут код... Я уже много лет хочу придумать как хотя бы выжимку из этого записать для youtube, но у меня пока нет идей.
@@tilir можете попробовать создать какой-нибудь Сишный проект средней величины, где сложность будет постепенно нарастать. Например, вы как-то хотели побольше поговорить про Vulkan, может было бы придумать сишный проект, связанный с 3D графикой, получится отчасти 2 в 1, +аудитория любит графику и задачи с геометрией, начать здесь можно даже не с Vulkan, а OpenGL или даже вообще просто теории на SDL с ускорением только непосредственно на вывод, а всё остальное на CPU, причём такие проекты подразумевают активную работу с памятью, а C мы любим как раз в тех случаях, когда хочется поработать с памятью. А зная вас (в хорошем смысле) вы потом всех заставите всё переписать на C++ так, чтобы проект из одного приложения мог запускаться в режиме софтверного, OpenGL и Vulkan рендеров :).
Здравствуйте, Константин Игоревич! Насколько я понимаю на 2:00 используется дополнительный код для битового представления числа и вследствие этого диапазон значений должен быть от -8 до 7
Нет. Единственный способ это сдать мне первый уровень. Это связано с тем что нет никакого определенного второго уровня. Ваш второй уровень зависит от ряда решений, принятых вами в первом.
@@tilir в продолжение предыдущего вопроса... Ответ, вероятнее всего, очевиден, но вдруг :). А существует ли какой-нибудь способ сдать вам первый уровень не являясь студентом МФТИ?
Преподаватель так красиво делает акценты своими словами "Если бы мы говорили о языке X, то на этом было бы все", потом таинственно замолкает, а я уже хлыщу слюной и кричу в экран "Боже, расскажите мне еще, почему С++ так мега хорош!"
Удивлен, что в задаче про пересечение треугольников на плоскости коэффициенты в уравнении прямой так страшно выводятся: синусы, косинусы, углы... Если без нормировки, то я бы использовал две операции - вычитание и умножение. С нормировкой - чуть сложнее, но совсем не так. Вы намеренно показываете этот пример как требующий доработки?
Я вроде проговорил словами. Это выглядит страшно, но это сделано не случайно. Сделать лучше можно. Но скорее всего первая попытка сделать лучше провалится.
а можно немного не по теме вопросец ? а почему вектор нормали к прямой имеет координаты (-sin_angle, cos_angle)? то есть непонятна 4ая строчка конструктора line_t . таким образом a и b у нас получаются могут принимать значения всего лишь в отрезке [-1,1]. Почему выбрано общее уравнение прямой для её задания, а почему нельзя было выбрать y=kx+b?
Извините за оффтоп, но хотел задать вам вопрос. Можете порекомендовать литературу по алгоритмам и структурам данных? У меня не было систематического образования по программированию и решил подтянуть этот недочет. Первое, что мне порекомендовал интернет, это Thomas H. Cormen "Introduction to Algorithms", но может быть у вас есть другие рекомендации по этой теме. За ролик спасибо, вы единственный канал, на который я решил прожать колокольчик.
Да, Кормен отличная книга. С него и начните для общего обзора. А дальше всё зависит от агенды: для специфических областей (вычислительная геометрия, например, или там бэкенды компиляторов) обычно есть свои книжки толщиной примерно с Кормена.
@@tilir А о книге С. Л. Бабичева что скажете (доцент институтской кафедры информатики и вычислительной математики МФТИ, кафедры алгоритмов и технологий программировния)? «Лекции по алгоритмам и структурам данных». В бумажном виде издана в 2019; последняя версия этой книги, 2021 г., в электронном виде, с исправленными опечатками, есть на его сайте, в разделе «Мои книги». Лекции и семинары по этой книге автор ведет в ВМК МГУ. Книгу Кормена автор также характеризует как «прекрасную» в предисловии, однако отмечает и некоторые отличия своего курса от других, основанных как на книге Кормена, так и другого рода. От курсов по алгоритмам, основанных на книге Кормена, книга Бабичева отличается тем, что в ней есть примеры на Си и C++; от курсов же алгоритмов другого рода она отличается тем, что в них алгоритмы служат лишь иллюстрацией к свойствам языка.
@@vadimflyagin6506 я не читал, но сейчас нагуглил и пролистал. Как методичка к курсу -- очень даже. А так мне кажется, что все эти алгоритмы с примерами на C++ я уже где-то видел. У Седжвика например. Или у Вейса. Вообще весь этот класс книг "алгоритмы с примерами на C++" имеет один общий недостаток: там обычно реализация довольно наивная, на уровне того же псевдокода, то есть в реальном мире реализация будет отличаться кардинально. В плюсы Бабичу можно записать, что он хотя бы не сильно увлекается ООП в примерах, от ООП в исполнении Вейса у меня глаза кровью вытекали. Поэтому я всем рекомендую Кормена. Там используется псевдокод, а значит реализация остаётся читающему как упражнение.
@@tilir вот кстати не посоветуете книги по вычислительной геометрии? Для общего развития читала Кормена, но там именно по вычислительной геометрии ну очень мало материала, а вычислительная геометрия, это та сфера, где в реальной жизни можно дел наворотить, т.к. проблемы могут возникнуть даже при создании тестов, можно не учесть возможные решения или граничные ситуации или если где-то что-то пересекается за границами вычислимости (скажем если две плоскости ПОЧТИ параллельны, а прямая пересечения плоскостей чёрт знает где). Иногда конечно можно схитрить и просто объявить все "неудобные" варианты ересью (если контекст задачи не подразумевает полётов в другую галактику в поисках прямой от пересечения двух почти параллельных плоскостей), но это не слишком прилично).
@@victormustya1745 на самом деле это очень интересный вопрос. Есть причины по которым NULL не может быть void* в C++. И я об этом действительно не подумал когда готовил лекцию. Но детей год учили языку C. Мне кажется методически правильно сказать что NULL это что-то вроде void* и далее призвать всегда использовать nullptr.
Интересно как должен выглядеть sizeof(char) sizeof(int) и CHAR_BIT для машины, которая может адресовать только 48-битные слова? Вообще С на нее переносим с вездесущими zero-terminated-strings?
58:30 пытаюсь вкатиться в с++ из c#. В C# у структур и классов серьёзное отличие в том, что структуры располагаются в стеке, а классы - в куче. С++ это не так чтоли?
Это вы разыменовали вне функции bar, у вас при ошибке вызова собственно и не произойдёт. Передать "null-reference" законным образом нельзя (такого объекта просто нет в языке), null-pointer -- очень легко.
Когда мне начинают рассказывать прохладные истории о TDD, я обычно привожу один аргумент. Классики этого подхода (Фаулер, например) под "тестами" понимали именно что модульные тесты. То есть, несколько упрощая, перед тем как написать класс line_t мы пишем модульный тест, где проверяем, что конструктор line_t из двух point_t делает то-то и то-то. А вот потом уже пишем реализацию. Но де факто написать такие тесты и означает спроектировать типы данных над которыми работает программа. Так что с этого и надо начать.
Ни разу не видел чтобы E2E писались до того момента когда готова хоть какая-то черновая реализация. Они довольно бессмысленны пока нечего собственно тестировать.
@@tilir спасибо за отклик, тем более за такой оперативный 👍 Вы , конечно, человек очень умный , снимаю шляпу перед вами . Но Вы уже не первый кто делает ставку на удаленку. Причем тот кто об этом мне говорил тоже является очень опытным и умным плюсовиком . Как вы вот себе её (удаленку) представляете ??? Скажем человек уровня джуниора хочет попробовать на просторах плюсов. Вы считаете , что кто-то будет с ним иметь дело на удаленке, как он сможет доказать свою работоспособность и то что справится, а не начудит, то есть как этому джуниору заочно можно доверять? да и у кого он будет набираться опыта ведь для этого нужен коллектив или кто-то опытный кто рядом ??? (интересно было бы почитать, что об этом думаете)
Стажироваться лучше очно. Для этого в Москву лучше приехать хотя бы на пару лет (и это само по себе полезно особеннно в молодости). Далее после 2-3 лет опыта всё станет проще.
А для чего так усложнять? То же самое можно было объяснить быстрее и проще, а осободившееся время использовать для примеров и их разбора. Студентов искренне жалко. На прошлой лекции лектор сказал "0 домашних заданий выполняет примерно половина группы". Понятно почему. Сложность.... даже скорее не сложность, а запутанность подачи материала "зашкаливает". Вопрос: для чего мы выбрали как пример задачу про пересечение треугольников? Она сложная. Почему именно эта задача (по мнению лектора) хорошо подходит для раскрытия темы "Указатели и ссылки"?
Студентов жалеть не надо, это факультатив. Кто не хочет, тот просто прекращает ходить. Треугольники это отличный выход на инкапсуляцию. Инкапсуляция создаёт контраст с указателями так как это разные миры. В конце лекции всё сшивается в одно.
Программирую на с++ больше 20 лет, лекцию посмотрел с удовольствием, завидую студентам Константина
Как здорово, что технологии позволяют учиться программированию даже не поступая в МФТИ )
от студентов школы-21 огромное Вам спасибо за Ваш труд !!
Спасибо. А что такое школа-21?
@@tilir Школа программирвоания, проект Сбера и 2гис )
@@АндрейМисюров насколько знаю только в Новосибе 2гис причастен к школе
@@tilir я тоже из неё) это бесплатная школа без преподавателей, куда можно прийти с нуля, в начале идёт достаточно много программирования на СИ, потом с++, потом более широкоиспользуемые языки. Особенность обучения, что нет преподавателей, только задания и куча людей, которые тоже эти задания делают, сдачи друг другу по чек листам, автоматические проверки в начале обучения, кодревью друг друга начиная с плюсовых проектах.
Основное обучение достаточно длительное, 2-3 года
вам платят за рекламу? Не нужно здесь ничего рекламировать, этот канал НЕ для ботов СБЕРА!!!!@@polinakononova8453
Очень понравились Ваши лекции, спасибо за то, что делитесь с нами
Благодарю вас за лекции, лучшего преподавателя невозможно даже вообразить!
Классная картинка 13:30 , люблю такую подачу материала. Спасибо за лекцию.
емае, спасибааааааааа, мне 16 лет, углубляюсь в С++, очень нравятся ваши лекции!
Отличная лекция , спасибо !)
Спасибо за лекцию.
59:05 "Что вам мешает привести указатель на ваш объект к char* и поправить там все что угодно инкапсулированное".
Мне кажется на практике нам мешает не только совесть, но и формальное UB с точки зрения стандарта. А именно, насколько понимаю, по стандарту мы можем использовать арифметику указателей только в пределах одного массива (и одного значения за его пределами), причем тип указателя должен совпадать с типом массива. Иначе - UB.
В дрфате стандарта C++20 есть по этому поводу 7.6.6 [expr.add]:
(4) When an expression J that has integral type is added to or subtracted from an expression P of pointer type, the result has the type of P.
(4.1) - If P evaluates to a null pointer value and J evaluates to 0, the result is a null pointer value.
(4.2) - Otherwise, if P points to an array element i of an array object x with n elements (9.3.4.5), the expressions P + J and J + P (where J has the value j) point to the (possibly-hypothetical) array element i + j of x if 0 ≤ i + j ≤ n and the expression P - J points to the (possibly-hypothetical) array element i − j of x if 0 ≤ i − j ≤ n.
(4.3) - Otherwise, the behavior is undefined.
...
(6) For addition or subtraction, if the expressions P or Q have type “pointer to cv T”, where T and the array element type are not similar (7.3.6), the behavior is undefined.
[Note 2 : In particular, a pointer to a base class cannot be used for pointer arithmetic when the array contains objects of a derived class type. - end note]
В данном случае мы попытаемся применить арифметику указателей к объекту, не являющемуся массивом, что в соответсвии с пп (4.2, 4.3) является UB. Не уверен что при желании это нельзя обойти, но кажется наивный подход порочен и по формальному слову стандарта, а не только по совести.
Хотя, конечно, на практике это сработает как ожидается)
UPD: Глянул на следущий слайд. Насколько понял судя по 6.8.1 [basic.types.general] значение для trivially copyable сначала можно скопировать в массив, а потом уже по нему идти, тогда это уже не будет UB с формальной точки зрения.
(2) For any object (other than a potentially-overlapping subobject) of trivially copyable type T, whether or not
the object holds a valid value of type T, the underlying bytes (6.7.1) making up the object can be copied into an array of char, unsigned char, or std::byte (17.2.1) If the content of that array is copied back into the object, the object shall subsequently hold its original value.
Но в любом случае, конечно не важно, можно ли это делать или нет, мы и правда не хотим так делать)
UPD2: На cppreference в статье Pointer declaration первый пример иллюстрирует эту проблему. Там структура из двух интов, но даже без всяких кастов к char* мы не можем без UB добраться от указателя на первый инт ко второму. И хотя мы можем инкрементировать один раз указатель на первый инт (в этом случае указатель ведет себя как указатель на массив из одного элемента, про это есть в сноске в [expr.add] 4.2), то разыменовать и обратиться к этому полю, это уже UB, даже если его тип и значение совпадают с другим указателем, который был получен честным взятием адреса второго поля.
Спасибо!
Спасибо за хорошую подборку литературы. Пишу на С++ 5 лет, но все равно интересно Вас слушать. P.S. 😂 Как всегда "залипла" в 4 утра , это действительно интересно. Про буддийских монахов и рис в конце отдельное спасибо.
P.P.S. Gilbert Strang ua-cam.com/play/PLE7DDD91010BC51F8.html&feature=shared
p[2] vs 2[p] дело в том что [] может быть перегружен для типа и p[2] может означать что угодно, 2[p] == *(p+2) :) например const num = sizeof(p)/sizeof(0[p]) :). Ой забыл отметить, прекрасные лекции - получаю редкое удовольствие от прослушивания, даже пива не хочется
Константин, здравствуйте.
Ребята неоднократно рекомендовали посмотреть ваши лекции.
Выглядит все солидно, но есть один вопрос по поводу решения задачи, а именно когда вы говорите, "Синус дешевая операция, sqrt немножко подороже, но тоже довольно дёшево".
Что вы имеете ввиду, и по сравнению с чем дешево?
На сколько я знаю, тригонометрические функции, да и тоже деление - это +-самые медленные операции с точки зрения процессора.
Тоже самое сложение работает быстрее в десятки раз.
Если я не прав, то поправьте.
Спасибо, и хотелось бы когда-нибудь поработать с таким спецом как вы.
Вы неправы. Вы транслируете чьи-то заблуждения без ссылки на источник. Я очень хочу когда-нибудь записать свои лекции для первого курса по C и ассемблеру, где я разбираю эти вопросы более подробно. Если кратко: в современных CPU просто нет такого понятия как цена инструкции. Любая инструкция на ALU, независимо от того целочисленная она или плавающая не стоит нисколько по сравнению с задержками на промахи кешей и миспредикты бранчей. Исключения остались где-то в области embedded, но они, в общем, исчезающие. Слишком длинный конвейер, слишком широкое окно планировщика.
В качестве упражнения вы всегда можете показать класс. Возьмите выложенный мной код и покажите как вы его ускорите за счёт лучшей арифметики.
@@tilir да, стоило прикрепить источник изначально, но не смог вспомнить где именно это видел. Теперь нашёл.
Это PBRT, physically based rendering, Third edition, наверняка слышали про ресурс.
Прикрепляю цитату:
«On current CPU architectures, the slowest mathematical operations are divides, square roots, and trigonometric functions.»
Авторы с NVIDIA, Google и иным аналогичным опытом.
Я не оспариваю ваши слова, а пытаюсь понять что имеете ввиду вы, и они.
Mike Acton так же делал отсылку к этой теме в одной из своих презентаций, по типу:
Sqrt это около 200-300 циклов.
Могу так же скинуть сорс, если необходимо.
Спасибо
quick-bench.com/q/BpfMi_9RSiIbi8uEMq_NVQA81q8
Вот ваши "200--300 циклов": 40 процентов на сферическом бенчмарке в вакууме. Если вы посмотрите на дизассемблер, там затраты скорее на загрузку и выгрузку значений, чем на арифметику. В реальном приложении не будет и сорока процентов.
Как говорил Линус Торвальдс, "talk is cheap, show me the code". Исходники для треугольников выложены. Берите и обгоняйте. В процессе многое поймёте.
@@tilir дело не во мне, мне до вашего и их уровня ещё стоит поковыряться с cs, а в том, что то, что говорите вы, идёт вопреки с мнением людей вашего уровня.
Мне было интересно узнать что имели вы под, « дешевые инструкции», и они, « это самые медленные инструкции».
Но вектор вы задали, поэтому разберусь когда руки дойдут.
ps: я доверяю людям, у которых опыта на гору больше, чем у меня, и просто стараюсь найти какую-то истину.
@@wndtn я же написал. Я имею в виду дешевизну по сравнению с промахом мимо L1 или с миспредиктом перехода. Грубо говоря между лишним бранчом и лишним квадратным корнем, я сделаю корень, не особо раздумывая.
Могу предложить забавное использование 2[p]: в шаблоне можем так "задетектить", что переданный объект является указателем или массивом, т.к. для перегруженных операторов такое не сработает.
В лекции есть один запутывающий момент: архитектура с шириной char в один бит не является соответствующей стандарту c++, который требует минимум 8 бит.
Константин, приветствую! Спасибо за крутые лекции )) Вот ухо моё зацепилось, хочу немного придраться 14:18 , 17:19 - "статический массив" - тогда уж "статический или автоматический массив"
Здесь речь идёт о статическом / динамическом массиве в плане известности его размера на этапе компиляции (т.е. статически). Но я согласен, надо было предупредить.
Забавно. Со времен учебника по Borland C++ всегда думал, что ссылки - это операция взятие адреса объекта (захват) по которому компилятору запрещена адресная арифметика (а null - отсутствие объекта).
Сейчас на java, интересно полный флеш-бек сделать.
01:00:26 "private и public - это не модификаторы на данные, это модификаторы на имена данных"
12:56 по поводу 2[p] фантазирую, что это может быть связано с AT&T синтаксисом как-нибудь, там ведь тоже 2(%rax) это "прочитай из памяти по адресу равному значение в rax + 2".
Константин, добрый день. На 5:50 вы говорите о RAM модели, что С++ работает в такой модели, и что объекты не отделены друг от друга. Приведу некоторые ссылки на стандарт. Согласно [intro.memory], "The memory available to a C++ program consists of one or MORE sequences of contiguous bytes."; согласно [intro.object], объекты занимают region of storage, которые имеют storage duration, и (за некоторыми исключениями) уникальные адреса, такие что объекты не пересекаются, а по достижению конца storage duration все указатели на любую часть этого региона инвалидируются; согласно [basic.compound], reinterpret_cast даёт новое значение указателя лишь для pointer-interconvertible типов, а иначе полученный указатель не будет указывать на новый объект (возможно необходим launder); согласно [ptr.launder], чтобы провести указатель через оптимизационный барьер, все байты хранилища, достижимые через новый указатель, также должны быть достижимы через старый указатель; согласно [expr.add], арифметика указателей определена лишь в пределах массивов, и для этих целей одиночные объекты считаются элементами гипотетического одноэлементного массива, а получение указателя вне массива приводит к UB. Учитывая всё это, мне кажется, что ваше утверждение неверно, и на самом деле (боюсь здесь формулировать идею) абстрактная машина работает с объектами, за которыми закрепляется область хранения (память), нежели с памятью, то есть всё таки абстракция суть (изолированные?) объекты, а адреса нужны для их различия (?). Можете прокомментировать? Благодарю.
Любой правильно выделенный объект занимает "region of storage". При этом стандарт молчалив о том что же это за storage, но ясно, что, в любой системе с виртуальной памятью, имеется в виду виртуальная память.
Тот факт что этот storage с необходимостью линейный следует из того что внутри любого правильно выделенного региона вы можете линейно (назад и вперёд) ходить с помощью char*.
Разумеется при доступе по неправильно выделенном адресу (сформированному вами случайно например), доступе за границы массива и т.п., вы получаете UB
Методически правильный способ думать о памяти в рамках базового курса по C++ это думать о линейной памяти с некоторыми ограничениями. А не думать об абстрактной машине, состоящей из каких-то изолированных островков (которые внезапно оказываются вовсе не изолированы, а лежат во вполне конкретных местах виртуальной памяти и т.д.). Второй способ правильный по стандарту, но учить так -- это было бы издевательство над второкурсниками.
добрый день. Материалы есть, а вот хотелось бы еще задания увидеть о которых упоминаете в курсе. Спасибо
34:31 возможна небольшая неточность: this может быть nullptr. Пример Rect* a = nullptr; a->foo(); и если foo() не обращается к полям, это еще и будет работать
То что вы написали это тривиальное UB, работать это "будет" до первого ветерка как и любое другое UB.
[expr.ref]: The expression E1->E2 is converted to the equivalent form (*(E1)).E2; the remainder of [expr.ref] will address only the first option (dot).
На 21 слайде (18:06) после уаеличения xptr на один должно быть &x[1], а не &x[0]
Спасибо за внимательность, досадная опечатка. Добавлю в errata.
57:40 Дружба - это магия, мы пока не будем о дружбе :)
Спасибо.
На слайде 17 (13:43) в pb должно быть значение 01101, а не 11001.
Ну и если душнить по максимуму, то не будет ли в такой модели проблем от того, что здесь значение по нулевому смещению? Если мы например попробуем вызвать *&pb приведет к разыменованию нулевого указателя, хотя объект валидный. Может лучше сдвинуть pb немного от начала? Заодно на слайде будет видно, что он задает смещение от начала адресного пространства, а не от себя (хотя это и так было видно на предыдущих слайдах).
Спасибо, отличное наблюдение. Я бы не характеризовал это словом "душнить". Нормальная педантичность. А вы можете предположить чему в этой целевой архитектуре может быть равен NULL чтобы мы не теряли первый бит? =)
@@tilir С NULL хитро придумано) Если оно не обязано быть 0, то можно любое значение из 23-31, которые помещаются в 5 бит, но не входят в доступный для адресации диапазон. Но наверное правильнее (хотя и менее удобно для слайда) просто доступную память мапить не с нуля, а первые несколько бит оставить зарезервированными под nullptr.
UPD: под само nullptr разумеется достаточно одного бита, но может быть полезно зарезервезировать рядом несколько невалидных адресов, чтобы заодно отслеживать не только разыменование непосредственно по nullptr, но и в случае если в результате невалидной адресной арифметики адресуем какие-то близкие адреса. На реальной машине как понимаю такого эффекта добиваются бесплатно, просто резервируя первую страницу памяти, так что любое обращение по первым 4кб, а не только по nullptr вызывают segmentation fault.
Хочешь оценить качество курса/книги по плюсам посмотри как объясняют указатели. Указатели - первая тема после обзорной лекции! ЙЕЕЕЕ! Указатель - это расстояние от начала памяти - ВАУ!! (реально первый человек в рунете от кого я услышал именно эту формулировку). И дальше пошло жонглирование кодом.... Возможно поэтому на 2300 просмотров всего 130 лайков. Очень грустно что чтобы понять БАЗОВЫЙ курс по плюсам нужно виртуозно владеть Си. И ещё грустнее что этого курса нет на ютупчике. Надеюсь в следующем релизе будет больше примеров из реального кода. А на дворе шел 2022й и рота красноармейцев
Я с вами совершенно согласен. Мой магистерский курс не воспринимается без базового, а базовый сложно воспринимается без моего курса по C для первокурсников. Но именно мой курс по C наименее подходит для выкладки: там трёхчасовые семинары и очень мало экшена, люди пишут код, я правлю какие-то ошибки, что-то объясняю, потом они опять пишут код...
Я уже много лет хочу придумать как хотя бы выжимку из этого записать для youtube, но у меня пока нет идей.
@@tilir можете попробовать создать какой-нибудь Сишный проект средней величины, где сложность будет постепенно нарастать. Например, вы как-то хотели побольше поговорить про Vulkan, может было бы придумать сишный проект, связанный с 3D графикой, получится отчасти 2 в 1, +аудитория любит графику и задачи с геометрией, начать здесь можно даже не с Vulkan, а OpenGL или даже вообще просто теории на SDL с ускорением только непосредственно на вывод, а всё остальное на CPU, причём такие проекты подразумевают активную работу с памятью, а C мы любим как раз в тех случаях, когда хочется поработать с памятью. А зная вас (в хорошем смысле) вы потом всех заставите всё переписать на C++ так, чтобы проект из одного приложения мог запускаться в режиме софтверного, OpenGL и Vulkan рендеров :).
Первый лайк, даааа
Правильно ли я понимаю, что ссылки на константы инициализируются на этапе исполнения, а не на этапе компиляции?
Ребят, а объясните пожалуйста более подробно трюк адресации с rip регистром, или наведите на ресурс где про это объясняется, буду сильно благодарен
Здравствуйте, Константин Игоревич! Насколько я понимаю на 2:00 используется дополнительный код для битового представления числа и вследствие этого диапазон значений должен быть от -8 до 7
Это же не целый тип из C++, это некая четырехбитная абстракция. Как скажете так и будет ))
@@tilir Путаете студентов :)
this удобно использовать для проверки самоприсваивания в операторах =, из this легко получить *this, что по сути и есть ссылка.
Про оператор= будет далее в лекции по RAII.
13:22 sizef(*p) * количество байтов в char - не обязательно у нас char однобайтный
А где-нибудь можно будет достать разные уровни домашних заданий, если не являешься вашим студентом?)
Нет. Единственный способ это сдать мне первый уровень. Это связано с тем что нет никакого определенного второго уровня. Ваш второй уровень зависит от ряда решений, принятых вами в первом.
@@tilir в продолжение предыдущего вопроса... Ответ, вероятнее всего, очевиден, но вдруг :). А существует ли какой-нибудь способ сдать вам первый уровень не являясь студентом МФТИ?
@@MX-xz4cy прислать мне его на почту?
Преподаватель так красиво делает акценты своими словами "Если бы мы говорили о языке X, то на этом было бы все",
потом таинственно замолкает, а я уже хлыщу слюной и кричу в экран "Боже, расскажите мне еще, почему С++ так мега хорош!"
Удивлен, что в задаче про пересечение треугольников на плоскости коэффициенты в уравнении прямой так страшно выводятся: синусы, косинусы, углы... Если без нормировки, то я бы использовал две операции - вычитание и умножение. С нормировкой - чуть сложнее, но совсем не так. Вы намеренно показываете этот пример как требующий доработки?
Я вроде проговорил словами. Это выглядит страшно, но это сделано не случайно. Сделать лучше можно. Но скорее всего первая попытка сделать лучше провалится.
студентам повезло.
а можно немного не по теме вопросец ? а почему вектор нормали к прямой имеет координаты (-sin_angle, cos_angle)? то есть непонятна 4ая строчка конструктора line_t . таким образом a и b у нас получаются могут принимать значения всего лишь в отрезке [-1,1]. Почему выбрано общее уравнение прямой для её задания, а почему нельзя было выбрать y=kx+b?
Я на лекции продемонстрировал студенческий код и явно это упомянул в т.ч. назвав автора. Вам предлагается написать лучше ))
@@tilir так я ж не против его кода)) просто хочу понять, ладно...
28:12 - в функцию конст ссылка передается, но на самом деле это указатель, то есть это указатель на конст данные получается?
Да, скорее всего аргумент являющийся константной левой ссылкой будет реализован через что-то вроде константного указателя на константные данные.
Извините за оффтоп, но хотел задать вам вопрос. Можете порекомендовать литературу по алгоритмам и структурам данных? У меня не было систематического образования по программированию и решил подтянуть этот недочет. Первое, что мне порекомендовал интернет, это Thomas H. Cormen "Introduction to Algorithms", но может быть у вас есть другие рекомендации по этой теме. За ролик спасибо, вы единственный канал, на который я решил прожать колокольчик.
Да, Кормен отличная книга. С него и начните для общего обзора. А дальше всё зависит от агенды: для специфических областей (вычислительная геометрия, например, или там бэкенды компиляторов) обычно есть свои книжки толщиной примерно с Кормена.
@@tilir понял, буду дальше осваивать, спасибо.
@@tilir А о книге С. Л. Бабичева что скажете (доцент институтской кафедры информатики и вычислительной математики МФТИ, кафедры алгоритмов и технологий программировния)? «Лекции по алгоритмам и структурам данных». В бумажном виде издана в 2019; последняя версия этой книги, 2021 г., в электронном виде, с исправленными опечатками, есть на его сайте, в разделе «Мои книги». Лекции и семинары по этой книге автор ведет в ВМК МГУ. Книгу Кормена автор также характеризует как «прекрасную» в предисловии, однако отмечает и некоторые отличия своего курса от других, основанных как на книге Кормена, так и другого рода. От курсов по алгоритмам, основанных на книге Кормена, книга Бабичева отличается тем, что в ней есть примеры на Си и C++; от курсов же алгоритмов другого рода она отличается тем, что в них алгоритмы служат лишь иллюстрацией к свойствам языка.
@@vadimflyagin6506 я не читал, но сейчас нагуглил и пролистал. Как методичка к курсу -- очень даже. А так мне кажется, что все эти алгоритмы с примерами на C++ я уже где-то видел. У Седжвика например. Или у Вейса.
Вообще весь этот класс книг "алгоритмы с примерами на C++" имеет один общий недостаток: там обычно реализация довольно наивная, на уровне того же псевдокода, то есть в реальном мире реализация будет отличаться кардинально. В плюсы Бабичу можно записать, что он хотя бы не сильно увлекается ООП в примерах, от ООП в исполнении Вейса у меня глаза кровью вытекали.
Поэтому я всем рекомендую Кормена. Там используется псевдокод, а значит реализация остаётся читающему как упражнение.
@@tilir вот кстати не посоветуете книги по вычислительной геометрии? Для общего развития читала Кормена, но там именно по вычислительной геометрии ну очень мало материала, а вычислительная геометрия, это та сфера, где в реальной жизни можно дел наворотить, т.к. проблемы могут возникнуть даже при создании тестов, можно не учесть возможные решения или граничные ситуации или если где-то что-то пересекается за границами вычислимости (скажем если две плоскости ПОЧТИ параллельны, а прямая пересечения плоскостей чёрт знает где). Иногда конечно можно схитрить и просто объявить все "неудобные" варианты ересью (если контекст задачи не подразумевает полётов в другую галактику в поисках прямой от пересечения двух почти параллельных плоскостей), но это не слишком прилично).
Около 11:00 --- всё таки, тип NULL в C++ не может быть void*. Он либо int, либо (C++11) std::nullptr_t
Я обычно видел в stddef.h нечто вроде:
#define NULL ((void*)0)
@@tilir там обычно как-то так:
#ifdef __cplusplus
#define NULL 0
#else
#define NULL (void*)0
#endif
Забавно. В честном сишном stddef.h какой-то ifdef на cplusplus? Правда? С чего бы?
@@tilir Ну, оно там есть. Возможно, это потому, что stddef.h также является стандартным заголовком C++
@@victormustya1745 на самом деле это очень интересный вопрос. Есть причины по которым NULL не может быть void* в C++. И я об этом действительно не подумал когда готовил лекцию.
Но детей год учили языку C. Мне кажется методически правильно сказать что NULL это что-то вроде void* и далее призвать всегда использовать nullptr.
Интересно как должен выглядеть sizeof(char) sizeof(int) и CHAR_BIT для машины, которая может адресовать только 48-битные слова? Вообще С на нее переносим с вездесущими zero-terminated-strings?
sizeof char обязан быть единицей по стандарту.
@@tilir согласен, не поспоришь :D Но как быть с остальным?
sizeof(char) == sizeof(int) && CHAR_BIT == 48 ?
sizeof(char) не всегда равен 1. Есть системы, где он равен 4.
Вы неправы. По стандарту он всегда равен 1. Есть системы где CHAR_BIT равен 32м.
58:30 пытаюсь вкатиться в с++ из c#. В C# у структур и классов серьёзное отличие в том, что структуры располагаются в стеке, а классы - в куче. С++ это не так чтоли?
Нет это не так.
На 57 слайде: foo(q) // это невозможно проделать с bar
Как-то не убедительно, а как же: bar(*q)
Это вы разыменовали вне функции bar, у вас при ошибке вызова собственно и не произойдёт. Передать "null-reference" законным образом нельзя (такого объекта просто нет в языке), null-pointer -- очень легко.
Около 40:00 --- фигня. Правильный ответ: начинать решать такую задачу надо с тестов
Когда мне начинают рассказывать прохладные истории о TDD, я обычно привожу один аргумент.
Классики этого подхода (Фаулер, например) под "тестами" понимали именно что модульные тесты. То есть, несколько упрощая, перед тем как написать класс line_t мы пишем модульный тест, где проверяем, что конструктор line_t из двух point_t делает то-то и то-то. А вот потом уже пишем реализацию.
Но де факто написать такие тесты и означает спроектировать типы данных над которыми работает программа.
Так что с этого и надо начать.
@@tilir не, я в данном случае про E2E-тесты. Задачка имеет много подводных камней
Ни разу не видел чтобы E2E писались до того момента когда готова хоть какая-то черновая реализация. Они довольно бессмысленны пока нечего собственно тестировать.
здорово всё это и интересно. но плюсы в 23 году в рф кроме москвы и Питера мало где востребована в нынешних условиях. курсы для Москвы видимо.
Как раз наоборот с нынешним развитием удалёнки все двери открыты любому кто одарён и работоспособен.
@@tilir спасибо за отклик, тем более за такой оперативный 👍 Вы , конечно, человек очень умный , снимаю шляпу перед вами . Но Вы уже не первый кто делает ставку на удаленку. Причем тот кто об этом мне говорил тоже является очень опытным и умным плюсовиком . Как вы вот себе её (удаленку) представляете ??? Скажем человек уровня джуниора хочет попробовать на просторах плюсов. Вы считаете , что кто-то будет с ним иметь дело на удаленке, как он сможет доказать свою работоспособность и то что справится, а не начудит, то есть как этому джуниору заочно можно доверять? да и у кого он будет набираться опыта ведь для этого нужен коллектив или кто-то опытный кто рядом ??? (интересно было бы почитать, что об этом думаете)
Стажироваться лучше очно. Для этого в Москву лучше приехать хотя бы на пару лет (и это само по себе полезно особеннно в молодости). Далее после 2-3 лет опыта всё станет проще.
@@tilir ну такой вариант, конечно, да.
ua-cam.com/video/e5__34DFz5Y/v-deo.html
А в четвертой?
А для чего так усложнять? То же самое можно было объяснить быстрее и проще, а осободившееся время использовать для примеров и их разбора. Студентов искренне жалко. На прошлой лекции лектор сказал "0 домашних заданий выполняет примерно половина группы". Понятно почему. Сложность.... даже скорее не сложность, а запутанность подачи материала "зашкаливает".
Вопрос: для чего мы выбрали как пример задачу про пересечение треугольников? Она сложная. Почему именно эта задача (по мнению лектора) хорошо подходит для раскрытия темы "Указатели и ссылки"?
Студентов жалеть не надо, это факультатив. Кто не хочет, тот просто прекращает ходить. Треугольники это отличный выход на инкапсуляцию. Инкапсуляция создаёт контраст с указателями так как это разные миры. В конце лекции всё сшивается в одно.
@@tilir Если это факультатив, то вопросы снимаются. Пересмотрю лекцию еще раз. Возможно я что то упустил. Спасибо за ответ.