Комментарии 77
Бимба, одним словом
Рандомный чувак после чтения статьи: -Ну вот я теперь и шарю в этом вашем С++.
Вы просто похитили и перелицевали известный анекдот: ... - "Хэнде хох!" - "Но это же немецкий..." - "А, ну значит еще немецкий знаю".
Да, ты прав, многие языки содержат в своем стандартном наборе если не идентичные, то очень похожие конструкции, особенно это касаться С-подобных языков. Я делал акцент конкретно на С++ из-за его популярности в учебных заведениях, а так же из-за возможности напрямую работать с памятью (указатели и разыменовывание), ведь так проще наглядно объяснить как это работает.
Речь, скорее шла о том, что С++ специфичных вещей тут довольно мало, и на самом деле здесь максимум расписан Си с классами. Да, если честно, даже и он не расписан. С++ раз эдак в 50 более объемный язык. Как синтаксически, так и... ну скажем "исторически", в плане костылей.
Но в целом статья сразу предупреждает, что она не для того, чтобы выучить (и упаси боже понять) С++, а для того чтобы тупо сессию сдать. В этом смысле сойдёт, хотя я по диагонали проглядел
Ради сдачи сессии и не стоит знать больше, большему в универе ничему и не учат в принципе)
Лично мне было полезно, для общего понимания устройства программ, узнать про то как языки работают с памятью. До этого я смутно себе это представлял.
Очень ошибочное понимание, потому что здесь очень много упущено даже из базового линковка, стек работа динамической памяти, утечка памяти, даже число int не занимает 4 байта в общем случае и тем более RAM в комп'ютерах имеет размер ячейки равный битность компьютера.
RAM в комп'ютерах имеет размер ячейки равный битность компьютера
А вот тут неточность. Размер ячейки RAM ровно 1 бит. Да и битность процессора к RAM прямого отношения не имеет.
А при чем здесь вообще RAM? Новичок обычно работает с десктопной платформой: Linux, Windows и т.п. На таких платформах ваша программа будет работать с виртуальной памятью, а не с RAM. Доступа к RAM как таковой у вас нет. С RAM работает ОС и вас непосредственно к RAM она не подпустит.
Ссылки ещё.
Да тут и string-то нету. Сказано только, что есть модуль для работы со строками. На этом и закончили)
Второй способ называется сложение с присваиванием
Если быть педантичным, то +=
- это присваивание со сложением. Такие операторы называются операторами compund assignment и на них распространяются все правила операторов присваивания.
float a = 5 / 2;
Ура, дробное число получено, можешь отметить этот день в своем календаре)))
Что за ерунду вы пишете? Это же классика С и C++ FAQ. 5 / 2
всегда равно 2 и никогда не дает "дробное число" в том смысле, что переменная a
получит значение 2.0, а не 2.5. Для целочисленных операндов /
- это операция целочисленного деления.
Создадим переменную с новым типом данных, логическим. Он занимает всего 1 байт и может быть равен либо
0
, либо1
,0
этоложь
,1
этоправда
.
В языке C++ тип данных bool
принимает значения false
или true,
а не 0
или 1
. Это несколько иное.
Ссылочная переменная, хранит в себе исключительно ссылку на первый байт переменной, на которую он ссылается
К чему здесь это оговорка про какой-то "первый байт"? Ссылка ссылается на переменную - этим все сказано.
Каждая ячейка памяти имеет свой адрес, записанный в шестнадцатеричной системе.
Где это он записан в шестнадцатеричной системе? А если я запишу адрес в десятичной системе, то это будет уже не адрес?
Если мы напишем нечто вот такое:
A + 1
, то увидим еще одну ссылку, как не трудно догадаться, это ссылка на вторую ячейку массива.
Вы уже ввели в своей статье разделение на указатели и ссылки. Почему же вы продолжаете упорно называть указатели ссылками?
"Нетрудно догадаться"? Вы все время замечаете, что это "ссылка на первый байт элемента". Поэтому по здравой логике "догадаться" мы скорее всего должны, что A+1
- это ссылка на второй байт, так? А это все таки ссылка на второй элемент. До этого непросто догадаться из-за ваших странных (и ненужных) уточнений про "первый байт".
Надеюсь ты еще помнишь, что массив хранит в себе адреса, по которым мы можем перемещаться,
Что? Массив int D[5][5];
не хранит в себе никакие адреса. Все адреса, которые вы используете для адресной арифметики по такому массиву, вычисляются "на лету". Они не хранятся нигде в массиве.
Функция должна быть объявлена до ее использования. Это значит что все функции будут располагаться вверху файла.
Не ясно, что значит "функции будут располагаться".
Да, верно. Уже поправил. Спасибо!
#include
- пишется в начале файла. Подключает так называемые библиотеки,
#include
не подключает никакие библиотеки. #include
включает стандартные заголовки или внешние заголовочные файлы. Это совсем другое.
<string>
- модуль со строковым типом данных, которого в "чистом" С++ нету.Небольшой экскурс. Строки, это на самом деле массивы символов, и чтоб не возникало возни с этим, создали модуль, который упрощает работу с ними. Если интересно как это устроенно на программном уровне, погугли "С-строки".
Но при при чем здесь C-строки? Заголовок <string>
не имеет прямого отношения к С-строкам.
P.S. И, конечно же, проверка правописания и tsya.ru. Трудно читать.
Неправда ваша
int a;
это непосредственно выделение тех четырех байт памяти под целое число
Далеко не всегда - зависит от компилятора и от разрядности системы. То, что вы описали - u_int8_t
Степень 31, а не 32 потому что первый бит отвечает за знак числа, если 0 то +, если 1 то -.
Справедливо только для прямого кода, которым не пользуются и практически никогда не пользовались
Это абсолютно то же самое, только короче.
Тоже нет (если умолчать об оптимизациях компилятора)
В первом случае сначала выделится память под переменную, а потом, отдельным действием, туда запишется значение, и с точки зрения С++ это будет 1 действие. Во втором - зависит от компилятора, но скорее всего будет 2 действия, с возможностью что-то считать между ними
Строки со второй по четвертую на самом деле делают одно и тоже: просто к значению переменной
c
прибавляют1
.
Опять-таки нет.
Строка с=с+1 сначала помещает с в регистр, потом - делает SUM C, 1, потом - записывает куда-то результат, и только потом изменяет ячейку памяти. с+=1 - команда не требует возможности обращения к промежуточному результату и экономит 1 такт на перезаписи.
На подобии команды
cout
есть функцияcin
Говорить о cin/cout, не рассказав ни слова о потоках - не совсем корректно, особенно если статья для чайников
Обрати внимание, что задать значение константе нужно сразу при объявлении, потом поменять его уже будет нельзя.
Если сильно надо - то можно. const_cast никто не отменял
Ну и к вопросу о указателях/ссылках - на дворе 2022ой, но в статье нет ни слова о умных указателях. И о том, что почти всегда вместо массива имеет смысл использовать stl::vector. Не всегда, но почти.
Справедливо только для прямого кода, которым не пользуются и практически никогда не пользовались
Вообще-то сделанное автором утверждение справедливо для всех практически используемых целочисленных знаковых представлений. Знаковый бит равен 1 - это отрицательное число и в прямом, и в обратном и в дополнительном коде (если считать "отрицательный ноль" отрицательным числом).
Если сильно надо - то можно. const_cast никто не отменял
Это как это??? Никакойconst_cast
не позволит изменить значение константного объекта. В С++ вообще не существует возможности изменить константный объект, т.е. любые попытки это сделать приводят к неопределенному поведению (кроме, конечно же, mutable
членов класса). И const_cast
к этому не имеет никакого отношения.
Строка с=с+1 сначала помещает с в регистр, потом - делает SUM C, 1, потом - записывает куда-то результат
с+=1 - команда не требует возможности обращения к промежуточному результату и экономит 1 такт на перезаписи.
К языку С++ все эти разглагольствования не имею никакого отношения. Если c
- это просто переменная, то c += 1
по определению эквивалентно c = c + 1
.
Знаковый бит равен 1 - это отрицательное число и в прямом, и в обратном и в дополнительном коде
По тексту читается (поправьте меня, если я ошибаюсь), что только знаковый бит отвечает за знак чиста. Грубо говоря, если его инвертировать - число поменяет знак.
Это слегка не так
Никакой
const_cast
не позволит изменить значение константного объекта
Документация говорит об обратном
Под спойлером пример - можете запустить и проверить
Программа
//g++ 7.4.0
#include <iostream>
int main()
{
const volatile int w = 10;
int &wr = const_cast <int &> (w);
wr = 20;
std::cout << w << std::endl; //output: 20
}
К языку С++ все эти разглагольствования не имею никакого отношения
Тут надо смотреть, во что оно скомпилируется. Если компилировать под 8086 без оптимизаций, например, то я полагаю, с+=1 перейдет в INC с, а c=c+1 - сложит и присвоит через SUM и MOV.
UPD: нашел в документации
Hidden text
![](https://webcf.waybackmachine.org/web/20220730213937/https://habrastorage.org/getpro/habr/upload_files/534/275/c9e/534275c9e25b138f2db6a13ee5e8534f.png)
Ссылка на пруф
https://en.cppreference.com/w/cpp/language/operator_assignment#Builtin_compound_assignment
Под спойлером пример - можете запустить и проверить
Данная программа не является программой на C++, так как содержит в себе undefined behavior.
const_cast
makes it possible to form a reference or pointer to non-const type that is actually referring to a const object or a reference or pointer to non-volatile type that is actually referring to a volatile object. Modifying a const object through a non-const access path and referring to a volatile object through a non-volatile glvalue results in undefined behavior.
Тут надо смотреть, во что оно скомпилируется. Если компилировать под 8086 без оптимизаций, например, то я полагаю, с+=1 перейдет в INC с, а c=c+1 - сложит и присвоит через SUM и MOV.
Тут не надо ничего смотреть кроме стандарта. Для интегральных типов это одно и то же. Как при этом ведет себя какой-либо компилятор - дело десятое. Тем более, что современный clang, что gcc сгенерируют один и тот же код с любым бэкэндом даже с -O0
Обновил коммент выше, приложил скрин документации.
Сокращенное сложение - атомарная операция, обычное сложение с последующим присваиванием - нет.
Нет, к атомарности это не имеет никакого отношения. Это работает только тогда, когда x+c вызывает какой-то сайд-эффект. Сложение интегральных типов никаких сайд-эффектов не порождает.
Грубейше неверно!
Вы уже второй раз "прикладываете документацию", но при этом выдумываете то, чего в этой документации нет даже отдаленно. Выделенное вами утверждение об "evaluated only once" не имеет никакого отношения ни к какой "атомарности".
Документация говорит об обратном
Еще она говорит что
Modifying a const object through a non-const access path and referring to a volatile object through a non-volatile glvalue results in undefined behavior.
У вас в примере классический экземпляр того самого убэ, которым принято пугать маленьких детей.
Грубо говоря, если его инвертировать - число поменяет знак.
Я не увидел там такой далеко идущей категоричности.
Документация говорит об обратном
Ничего подобного там не сказано. Документация по ссылке говорит именно то, что она должна говорить: const_cast
снимает константность с путей доступа к объектам, то есть с указателей или ссылок. На этом все. Там ни слова не сказано о некоем появлении разрешения на модификацию константных объектов.
Ваша "документация" нигде и никак не отменяет фундаментального правила С и С++: модификация константных объектов запрещена, т.е. приводит к неопределенному поведению. Пытаться модифицировать объект через путь доступа, полученный от каста, разрешается только если сам объект НЕконстантен.
Более того, по вашей же ссылке ясно сказано: "Modifying a const object through a non-const access path [...] results in undefined behavior".
Вроде бы - совершенно простой и логичный набор правил. Но почему-то он зачастую вызывает затруднения. Почему-то встречается верование, что const_cast
позволяет модифицировать константные объекты (или даже "предназначен для этого") ...
Программа
Ваша программа имеет неопределенное поведение. Она ничего не демонстрирует.
Если компилировать под 8086 без оптимизаций, например, то я полагаю, с+=1 перейдет в INC с, а c=c+1 - сложит и присвоит через SUM и MOV.
В С++ нет таких понятий как "8086", "без оптимизаций", "SUM и MOV".
нашел в документации
И? Что вы там увидели? Утверждение "evaluated only once" имеет какое-то значение только если c
- некое выражение с нетривиальным поведением и/или побочными эффектами. То есть, например, это гарантирует, что в f() += 1
функция f
будет вызвана только один раз. Если жеc
- просто переменная, то в c += 1
выделенный вами текст не значит вообще ничего.
Это все ненужные усложнения, ведь, как вы сами сказали, это "статья для чайников ". Во всех случаях фразы "это тоже самое" я имел ввиду результат, ибо настолько глубоких подробностей не знаю ни я, ни те для кого эта статья была написана. Но спасибо, было интересно узнать такие нюансы.
Говорить о cin/cout, не рассказав ни слова о потоках - не совсем корректно, особенно если статья для чайников
Отдельно стоит добавить о необходимости endl или flush при выводе в поток, иначе можно удивиться почему на экране ничего не появилось.
Справедливо только для прямого кода, которым не пользуются и практически никогда не пользовались
Кхе, кхе. :) Этот код ну очень любят военные. :)
Неплохой такой лонгрид для совсем нулей в C++, довольно неплохо написано, но местами можно было и покороче... В конечном итоге все эти знания уйдут после первого часа изучения Python
Выглядит как доклад студента, сдавшего наконец сессию с 3 раза ;) Для шпаргалки букв слишком много, для справочного материала - слишком мало.
К сожалению мало кто пользовался ею,
Верю. Читать тяжело, и систематизировано всё достаточно плохо. Из С++ просматривается только cin,cout,new. Предложение гуглить из шпаргалки про С-строки это сильно ;)
Есть над чем работать ;)
А приведите примеры, какие пет проекты на С++ были бы в тему начинающим на этом языке ?
Во-первых, раз уж сказали про указатели и ссылки, важно сразу рассказать, что такое висячие ссылки, откуда они могут появиться и к чему они приводят. Это C++, он подобных ошибок не прощает, и надо готовить будущего разработчика к такому с самого начала.
Во-вторых, писать using namespace std; даже в начале .cpp -файлов во многих проектах считается плохой практикой, потому что может привести к очень неочевидных ошибкам.
В-третьих, вместо #include <math.h> лучше писать #include <cmath>, вы всё-таки на C++ пишете же, в не на Си.
В четвёртых,
<string> - модуль со строковым типом данных, которого в "чистом" С++ нету
<string> является частью стандарта C++, поэтому не надо вводить читателя в заблуждение.
Любая программа, будь то на телефоне или компьютере, строится на взаимодействии с оперативной памятью устройства.
Далеко не любая. Можно написать программу просто работая с регистрами (их мы же ОЗУ не будем называть?). И ввод/вывод тоже взять с портов прямо в регистры.
а в 4 байтах именно 32 бита
Байты были очень разные. Те, которые по 8 бит не единственные.
Степень 31, а не 32 потому что первый бит отвечает за знак числа, если 0 то +, если 1 то -.
Он не первый, а 31. И для беззнакового числа он не используется. Для знакового это просто число в дополнительном коде, который сейчас почти везде. Единственное, где это верно, так это прямой код.
Ты можешь
Использовать такое обращение к читателю как-то не очень принято…
Строка int a; это непосредственно выделение тех четырех байт памяти под целое число.
Компилятор BC31 повесился. У него int только два байта. О, горе ему! :)
где-то в памяти 4 байта
Надесь, не в куче? :)
нечто вот такое: 00000000 00000000 00000000 00000001
А тут заплакали процессоры с big-endian нотацией. :) У них такого числа не получилось в памяти. Увы. :)
Кстати, внимательней посмотрел. Нет, тут плачут little-endian процессоры. Ну или вы неправильно прочли память, куда записали 1. Думаю, всё-таки последнее.
Это абсолютно то же самое, только короче.
Ну-ну. А поменяйте-ка int на какой-нибудь класс и посмотрите, будет ли вызываться операция присваивания в этом случае. :)
Более того, многие среды разработки для С++ не дадут запустить код, если вы попытаетесь прочитать значение у переменной, которой не было задано значения.
Не встречал. А, кстати, переменные static по стандарту инициализируются нулём. Всегда.
Все три способа на программном уровне работают одинаково
C++ и ++C точно работают одинаково? Временный объект не создаётся в первом случае?
a = 3.5;
Компилятор предупреждение, что float присваивается значение double не написал?
есть функция
А, это функция… Буду знать. Я-то думал, что это объекты.
В них имена переменных не могут повторяться.
Интересно, а как вы, имея глобальную переменную A, используете в функции локальную переменную A?
Нумерация в массивах, и вообще в программировании в целом, начинается с нуля,
Тут зарыдал паскаль. И по-моему, ещё и бейсик (но я его уже больше 20 лет не использовал, так что это не точно).
A[i] = i;
А попробуйте i[A]=i. :)
имеет свой адрес, записанный в шестнадцатеричной системе.
Стесняюсь спросить, чем не устроили другие системы счисления? :)
значения 4, 5 и 2 являются константами.
Есть один способ… Она ведь тоже имеет тот самый адрес «в шестнадцатеричной системе счисления».
но на практике больше чем две не делают
Вот скажите на милость, вы как это узнали-то? У вас столь богатая практика, что вы можете за все-все задачи это утверждать? :)
Надеюсь ты еще помнишь, что массив хранит в себе адреса,
Это имя массива при обращении становится указателем на первый элемент. Но оно ни в коем разе просто указателем не является. Был какой-то фокус на эту тему, забытый мной за давностью лет.
Сразу скажу что разница будет ровно 20
Я бы не стал бы этого утверждать. Всё-таки, помнится, разность указателей зависит от устройства адресации памяти процессором.
про динамические двумерные массивы.
А удалять-то эти самые массивы когда будем?
#include — пишется в начале файла.
В любом месте, где вы хотите физически встроить кусок текста из #include.
Подключает так называемые библиотеки
Всего лишь подключает заголовочные файлы.
using namespace std — Подключает стандартное пространство имен. Что это такое пока не важно, это из ООП.
А, вот оно что… Я тут вспомнил: точно, Objective C — вот ООП. А C++ — это Си с классами, это не ООП. :)
то все придется писать с std:: в начале.
Помнится, именно так и рекомендуется делать. Иначе можно легко где-нибудь создать свой тип vector, например. И он пересечётся с тем, который вы опрометчиво открыли из std.
string — модуль со строковым типом данных, которого в «чистом» С++ нету.
Он смылся, когда отмывали Си++ до чистоты? :)
И вот я сижу на очередной паре и вставляю спички в глаза, ибо все это я успел выучить за первые две недели целенаправленного погружения в это «болото».
Я это «болото» за 22 года так и не выучил до конца. :) Поэтому, я вам завидую. Честно. :) Мне-то уже 39, а у вас всё впереди.
Удачи! :)
C++ и ++C точно работают одинаково? Временный объект не создаётся в первом случае?
В спецификациях префиксного и постфиксного ++
нет ни слова ни о каких "временных объектах". Зачем их сюда притягивать?
Ну-ну. А поменяйте-ка int на какой-нибудь класс и посмотрите, будет ли вызываться операция присваивания в этом случае. :)
Инициализация, разумеется, не имеет никакого отношения к "операции присваивания", но по-моему достаточно понятно, что автор и не хотел этого сказать.
Я бы не стал бы этого утверждать. Всё-таки, помнится, разность указателей зависит от устройства адресации памяти процессором.
Нет. Разность указателей T *
в С и С++ зависит только от того, сколько элементов типа T
содержится между этими указателями. Адресная арифметика в этих языках специфицирована на высоком уровне и от устройства адресации процессора не зависит. Стоит заметить, что вычитать друг из друга в С и С++ разрешается только указатели на элементы одного и того же массива.
В спецификациях префиксного и постфиксного ++ нет ни слова ни о каких «временных объектах». Зачем их сюда притягивать?
А это чтобы не было потом мучительно больно.
но по-моему достаточно понятно, что автор и не хотел этого сказать.
Что он хотел сказать я не знаю, а сказал он что никакой разницы нет.
Разность указателей T * в С и С++ зависит только от того, сколько элементов типа T содержится между этими указателями.
Для массива вы правы. В Си это гарантируется только в рамках одного массива. Вычитать же указатели из разных частей программы приведёт к неопределённому поведению. Автор вычитает массив, да.
Вычитать же указатели из разных частей программы приведёт к неопределённому поведению.
WAT?
Результат вычитания может не поравиться, но откуда возьмется неопределенное поведение?
Результат вычитания может не поравиться, но откуда возьмется неопределенное поведение?
Возьмётся от возможного неожиданного/неопределённого результата. Вы просто получите странные вещи. На одной платформе всё будет работать. На другой не будет. Так как-то.
Тут бы пример ;) Я не вижу никаких препятствий в вычитании любого указателя из любого, хоть и NULL (но зачем это может понадобиться, продолжаю не понимать).
А где может понадобиться… Так сходу и не придумать. В embedded, возможно, где-нибудь.
Как на счёт вычитание указателей на память, выравненную по разным границам?
Условно:
auto diff = ((int*)0x4) - ((int*)0x3).
Я пишу на С++ уже 9 лет, но понятия не имею, какой будет результат
С просто создан для стреляния в ногу ;) Но думаю что будет 1. Вы же не собираетесь в этот указатель 0х3 что-то писать?
А я думаю будет 0 :) Потому по логике операция вычитания указателей должна дать количество элементов типа T, которые поместятся между двумя адресами.
Что касательно писать в 0х3 - нет ни одного правила в стандарте, запрещающего мне это сделать.
А что вы туда запишете и откуда вы знаете, что по этому адресу существует объект? Более того, как вы в этом убедите компилятор?
*((int*)0x3) = 0xDEADBEAF;
?
А объект там и не должен существовать. Это может система с физической адресацией, а адрес 0x3 - это захардкоженный адрес, который мапится на совершенно другое оборудование в системе (не RAM), которое способно понять, что такое 0xDEADBEEF
.
Вы "пишете на С++ уже 9 лет", но до сих пор не имеете представления о том, что означает термин неопределенное поведение?
Нет никакого ответа на вопрос о том, "какой будет результат", так как в С++ не существует такой операции вычитания вообще. Попытки предсказания каких-то "результатов" свидетельствуют лишь о некомпетентности их совершающего.
О, гспди. Я отвечал на комментарий, который просил привести пример неопределенного поведения. Я привёл такой пример и специально подчеркнул, что понятия не имею, какой будет результат, как бы намекая на то, что определение будет неопределённым.
Так что здесь мы наблюдаем свидетельство появление зануды, не умеющего читать между строк.
Результат вычитания может не поравиться, но откуда возьмется неопределенное поведение?
Из спецификации языков С и С++. Где ясно сказано, что вычитание указателей разрешено только для указателей на элементы одного и того же массива (или на воображаемый элемент после последнего).
на воображаемый элемент после последнего
Это какое то еретическое толкование священного текста ;) Если можно работать с N+1 элементом - индукция нам говорит, что так же можно работать с любым (тоже воображаемым, почему нет). Просто массив в С - непрерывная область памяти by design, и разница между указателями на разные ее части осмыслена. А так, считать разницу между любыми указателями никто не запрещает. Может, кому то хочется иследовать логику работы аллокатора памяти.
Стандарт таки запрещает. А у вас шаг индукции необоснован.
А так, считать разницу между любыми указателями никто не запрещает. Может, кому то хочется иследовать логику работы аллокатора памяти.
Превращайте указатели в intptr_t
или uintptr_t
и исследуйте на здоровье, только так.
Нет, конечно. Это прямое и буквальное толкование священного текста, который вы вообще пытаетесь игнорировать.
Никакой индукции тут нет и быть не может.
Вы почему-то не в курсе того простого факта, что главным источником неопределенного поведения (UB) являются не какие-то "области памяти", "адреса в указателях", "аллокаторы" и прочая белиберда. Все это - никому не нужное пионерское разглагольствование, которое не имеет никакого отношения к делу вообще.
Главным источником фактического "непредсказуемого поведения" всегда являлась и является внутренняя логика компилятора, который использует UB для выполнения оптимизаций. Как известно, эквивалентным определением неопределенного поведения является следующее:
Компилятор имеет право транслировать код в предположении, что условия, приводящие к неопределенному поведению, не возникают никогда
Если компилятор сумеет обнаружить, что вы где-то вычитаете друг из друга два "левых" указателя, компилятор имеет право вообще вышвырнуть этот участок кода из программы. Или заменить результат вычитания на безусловный 0
. И или на 42
. И он будет прав, ибо он имеет право полагать, что этот код никогда не будет выполняться.
Или он может вообще отказаться транслировать ваш код.
Компиляторы уже давно это делают. Мы уже прошли и через strict overflow semantics, и через strict aliasing semantics, и через возвращение нулевых ссылок/указателей на локальные переменные в функции, и еще много других компиляторных решений, основанных на UB, но почему-то все равно определенным индивидуумам трудно понять, что UB - это в первую очередь последствия оптимизационных решений компилятора, а не следствие свойств их аппаратной архитектуры.
Если вы пытаетесь вычитать "левые" указатели, то язык С++ открытым тестом вам говорит - это не вычитание вообще. А ваши разглагольствования про "непрерывную память" - это пустопорожние разглагольствования, которые никакого отношения к вопросу не имеют.
Как правильно заметил KanuTaH выше, хотите произвольно обращаться с адресами - приводите указатели к std::uintptr_t
и дальше делайте с результатами что угодно.
А это чтобы не было потом мучительно больно.
Временные объекты создаем мы сами при реализации перегруженных версий оператора постфиксного ++
. Но к встроенному оператору постфиксного ++
, работающему со встроенными типами, все эти соображения неприменимы. Они создают неверную иллюзию того, что встроенный постфиксный ++
будет выполнять какие лишние ненужные действия. Не будет.
Я сам предпочитаю использовать префиксный ++
из соображений обобщенной оптимальности, если мне не нужен явно именно постфиксный. Но, тем не менее, я считаю, что не стоит ради этого заниматься дезинформацией новичков. До оптимизаций они еще успеют добраться.
работающему со встроенными типами, все эти соображения неприменимы.
В том-то и дело, что автор встретился до сих пор только со встроенными типами. А расписал как в общем за всё.
Но, тем не менее, не стоит ради этого заниматься дезинформацией новичков. До оптимизаций они еще успеют добраться.
Стойте! Новичков?! :O Я так понимаю, написание статьи предполагает нифига не новичка. Он же чему-то научить хотел? Другое дело, что по неведомой причине, автор решил, что он уже изучил Си++. Я не знаю, откуда у него такая уверенность в себе, что он решил заняться обучением читателей. Оно, конечно, похвально, но рекомендовать эту статью именно новичкам я бы не стал.
Я сам предпочитаю использовать префиксный ++
А вот я люблю именно постфиксный. Каюсь. Но я его так использую только со встроенными типами. А так, мне просто приятнее глазу n++, а не ++n.
Был какой-то фокус на эту тему, забытый мной за давностью лет
char a[]="Hello, World!";
char *b="Hello, World!";
if(sizeof(a)==sizeof(b)) printf("Hello, World!\n");
Вспомнил. Взятие адреса от имени массива даёт указатель на первый элемент.
Нет, конечно. Никакого "взятия адреса" тут не нужно. Это правило называется "array type decay" или "array-to-pointer conversion".
Это правило говорит, что значение типа массив T [N]
может быть неявно преобразовано к типу указатель T *
. Получающийся в результате указатель указывает на нулевой элемент массива. Вот и все.
int a[10];
int *b=(int*)&a;
int *c=&a[0];
b и c -указывают на первый элемент.
Если же a заменить на динамический массив, то b станет указывать на указатель на этот массив, приведённый к int *.
Не понимаю, зачем вы взялись городить весь этот огород с (int *)
и &
(причем сразу с &
у вас не получилось и вы вынуждены были добавить приведение типа).
То, о чем обычно ведут речь в таких случаях, выглядит так
int a[10];
int *b = a;
int *c = &a[0];
assert(b == c);
// Здесь `b` и `c` указывают на нулевой элемент `a`
И не надо никакого взятия адреса или насильного приведения типа.
Если же a заменить на динамический массив
Что такое "динамический массив" и при чем он тут?
Не на "динамический массив", а на указатель. Вся суть в том, что вы заменяете a
на указатель. А уж на что он там будет указывать - на некий "динамический массив" или на дырку от бублика - роли не играет.
причем сразу у вас не получилось и вы вынуждены были добавить приведение типа
В каких-то старых компиляторах оно было не нужно. Но тому лет 20.
И не надо никакого взятия адреса или насильного приведения типа.
Это вообще-то пример, показывающий, что имя массива не указатель на первый элемент. Как только мы меняем это имя на действительно указатель на первый элемент массива, b и c становятся разными.
Не на динамический массив, а на указатель.
Вот конкретно тут вы и так поняли, что я имел в виду. ;)
В каких-то старых компиляторах оно было не нужно. Но тому лет 20.
В стандартном С++, как впрочем и в стандартном С, код
int a[10];
int *b = &a;
является некорректным, т.е. некомпилируемым. Все остальное - баги кривых компиляторов или, скорее, неправильная интерпретация диагностических сообщений пользователями.
Это вообще-то пример, показывающий, что имя массива не указатель на первый элемент.
С этим никто и не спорил. Имя массива - ни в коем случае не указатель на первый элемент. В rvalue-контекстах имя массива может лишь неявно конвертироваться к значению указателя на первый элемент. Именно это сказано в правиле "array type decay", которое я привел выше.
Все остальное — баги кривых компиляторов или, скорее, неправильная интерпретация диагностических сообщений пользователями.
А когда компиляторы были не кривые? Я с gcc 2.95 когда переносил программу на последний, знаете, сколько именно error он надавал? :) А 2.95 пофиг вообще было.
С этим никто и не спорил.
Тогда вы потеряли контекст беседы. Потому что всё началось с того, что я указал про один из фокусов, показывающих, что имя массива не есть указатель на первый элемент. Дальше я вспомнил, что это был за фокус. А дальше вы зачем-то подключились и заново повторили ровно то, что я в исходном сообщении и написал. А дальше с чем вы спорили я уже не знаю. Я же просто расписал, что имелось в виду под фокусом. Вот и всё.
Был какой-то фокус на эту тему, забытый мной за давностью лет
Насколько помню, перед указателем на первый элемент хранится длина массива
Но да, вы более предметно и подробно описали то, что я упоминал выше, а часть нюансов не заметил и я. Респект!
Изучение ассемблера - корень познания остальных языков. С++ в реальности сложен (в универе всё может быть проще если преподаватель не "гик"); плюсы сложны, потому что далеко не всегда знаешь что под капотом, а помнить все правила языка невозможно, поэтому постоянный доступ к интернету - must have любого программиста. Язык С - как следующий уровень после ассемблера. На С, думаю, с опытом можно программировать почти без доступа к интернету - хватит практики и пары книг. Кажется, что С - золотая середина между машинными кодами и, например, python. Но с другой стороны прогресс не остановить и придется вникать и в range, и в corutine и т.п. C++ кажется сближается с Python, равно как Windows с Linux :)
Я как то не ожидал, что автор реально начнет учить). Блин.. Название статьи было каким то провокационным, а тут гайд..
Вообще, на самом деле, эти звездочки должны быть рядом с названием типа данных, вот так:
int*
,int**
...
Звёздочки это модификатор типа переменной(или константы), и влияет она исключительно на имя переменной следующей за ней.
Например вот так:
int v = 0xDEADBEEF, b = 0xFFFF;
int *p = &v, &r = v, i = v;
std::cout << std::hex << ((i >> 16) & b) << ", " << (*p & b) << ", " << ((r >> 4) & (b >> 4)) << std::endl;
---
dead, beef, bee
На мой взгляд начинать обучение с переменных это не самый верных подход. Т.к. даётся много информации до того, как будет получен реальный результат.
Например я лично в учебнике Паскаль с нуля (https://ru.wikibooks.org/wiki/PascalABC.net_с_нуля) начинаю с констант. Не надо занимать подробными обьяснениями про "выделения памяти" и прочее. Зато сразу можно получить визуальные результат работы программы.
Фактически тот, кто правит конфигурационный файлы готовый программы уже занимается программированием на самом начальном уровне.
А переменные даются только в 4-м уроке(всего их 7).
С++ для тех кто не шарит