Вы "пишете на С++ уже 9 лет", но до сих пор не имеете представления о том, что означает термин неопределенное поведение?
Нет никакого ответа на вопрос о том, "какой будет результат", так как в С++ не существует такой операции вычитания вообще. Попытки предсказания каких-то "результатов" свидетельствуют лишь о некомпетентности их совершающего.
Нет, конечно. Это прямое и буквальное толкование священного текста, который вы вообще пытаетесь игнорировать.
Никакой индукции тут нет и быть не может.
Вы почему-то не в курсе того простого факта, что главным источником неопределенного поведения (UB) являются не какие-то "области памяти", "адреса в указателях", "аллокаторы" и прочая белиберда. Все это - никому не нужное пионерское разглагольствование, которое не имеет никакого отношения к делу вообще.
Главным источником фактического "непредсказуемого поведения" всегда являлась и является внутренняя логика компилятора, который использует UB для выполнения оптимизаций. Как известно, эквивалентным определением неопределенного поведения является следующее:
Компилятор имеет право транслировать код в предположении, что условия, приводящие к неопределенному поведению, не возникают никогда
Если компилятор сумеет обнаружить, что вы где-то вычитаете друг из друга два "левых" указателя, компилятор имеет право вообще вышвырнуть этот участок кода из программы. Или заменить результат вычитания на безусловный 0. И или на 42. И он будет прав, ибо он имеет право полагать, что этот код никогда не будет выполняться.
Или он может вообще отказаться транслировать ваш код.
Компиляторы уже давно это делают. Мы уже прошли и через strict overflow semantics, и через strict aliasing semantics, и через возвращение нулевых ссылок/указателей на локальные переменные в функции, и еще много других компиляторных решений, основанных на UB, но почему-то все равно определенным индивидуумам трудно понять, что UB - это в первую очередь последствия оптимизационных решений компилятора, а не следствие свойств их аппаратной архитектуры.
Если вы пытаетесь вычитать "левые" указатели, то язык С++ открытым тестом вам говорит - это не вычитание вообще. А ваши разглагольствования про "непрерывную память" - это пустопорожние разглагольствования, которые никакого отношения к вопросу не имеют.
Как правильно заметил KanuTaH выше, хотите произвольно обращаться с адресами - приводите указатели к std::uintptr_t и дальше делайте с результатами что угодно.
Временные объекты создаем мы сами при реализации перегруженных версий оператора постфиксного ++. Но к встроенному оператору постфиксного ++, работающему со встроенными типами, все эти соображения неприменимы. Они создают неверную иллюзию того, что встроенный постфиксный ++ будет выполнять какие лишние ненужные действия. Не будет.
Я сам предпочитаю использовать префиксный ++ из соображений обобщенной оптимальности, если мне не нужен явно именно постфиксный. Но, тем не менее, я считаю, что не стоит ради этого заниматься дезинформацией новичков. До оптимизаций они еще успеют добраться.
Результат вычитания может не поравиться, но откуда возьмется неопределенное поведение?
Из спецификации языков С и С++. Где ясно сказано, что вычитание указателей разрешено только для указателей на элементы одного и того же массива (или на воображаемый элемент после последнего).
В каких-то старых компиляторах оно было не нужно. Но тому лет 20.
В стандартном С++, как впрочем и в стандартном С, код
int a[10];
int *b = &a;
является некорректным, т.е. некомпилируемым. Все остальное - баги кривых компиляторов или, скорее, неправильная интерпретация диагностических сообщений пользователями.
Это вообще-то пример, показывающий, что имя массива не указатель на первый элемент.
С этим никто и не спорил. Имя массива - ни в коем случае не указатель на первый элемент. В rvalue-контекстах имя массива может лишь неявно конвертироваться к значению указателя на первый элемент. Именно это сказано в правиле "array type decay", которое я привел выше.
Не понимаю, зачем вы взялись городить весь этот огород с (int *) и & (причем сразу с & у вас не получилось и вы вынуждены были добавить приведение типа).
То, о чем обычно ведут речь в таких случаях, выглядит так
int a[10];
int *b = a;
int *c = &a[0];
assert(b == c);
// Здесь `b` и `c` указывают на нулевой элемент `a`
И не надо никакого взятия адреса или насильного приведения типа.
Если же a заменить на динамический массив
Что такое "динамический массив" и при чем он тут?
Не на "динамический массив", а на указатель. Вся суть в том, что вы заменяете a на указатель. А уж на что он там будет указывать - на некий "динамический массив" или на дырку от бублика - роли не играет.
Нет, конечно. Никакого "взятия адреса" тут не нужно. Это правило называется "array type decay" или "array-to-pointer conversion".
Это правило говорит, что значение типа массив T [N] может быть неявно преобразовано к типу указатель T *. Получающийся в результате указатель указывает на нулевой элемент массива. Вот и все.
А при чем здесь вообще RAM? Новичок обычно работает с десктопной платформой: Linux, Windows и т.п. На таких платформах ваша программа будет работать с виртуальной памятью, а не с RAM. Доступа к RAM как таковой у вас нет. С RAM работает ОС и вас непосредственно к RAM она не подпустит.
C++ и ++C точно работают одинаково? Временный объект не создаётся в первом случае?
В спецификациях префиксного и постфиксного ++ нет ни слова ни о каких "временных объектах". Зачем их сюда притягивать?
Ну-ну. А поменяйте-ка int на какой-нибудь класс и посмотрите, будет ли вызываться операция присваивания в этом случае. :)
Инициализация, разумеется, не имеет никакого отношения к "операции присваивания", но по-моему достаточно понятно, что автор и не хотел этого сказать.
Я бы не стал бы этого утверждать. Всё-таки, помнится, разность указателей зависит от устройства адресации памяти процессором.
Нет. Разность указателей T * в С и С++ зависит только от того, сколько элементов типа T содержится между этими указателями. Адресная арифметика в этих языках специфицирована на высоком уровне и от устройства адресации процессора не зависит. Стоит заметить, что вычитать друг из друга в С и С++ разрешается только указатели на элементы одного и того же массива.
Вы уже второй раз "прикладываете документацию", но при этом выдумываете то, чего в этой документации нет даже отдаленно. Выделенное вами утверждение об "evaluated only once" не имеет никакого отношения ни к какой "атомарности".
Ничего подобного там не сказано. Документация по ссылке говорит именно то, что она должна говорить: 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 выделенный вами текст не значит вообще ничего.
Справедливо только для прямого кода, которым не пользуются и практически никогда не пользовались
Вообще-то сделанное автором утверждение справедливо для всех практически используемых целочисленных знаковых представлений. Знаковый бит равен 1 - это отрицательное число и в прямом, и в обратном и в дополнительном коде (если считать "отрицательный ноль" отрицательным числом).
Если сильно надо - то можно. const_cast никто не отменял
Это как это??? Никакойconst_cast не позволит изменить значение константного объекта. В С++ вообще не существует возможности изменить константный объект, т.е. любые попытки это сделать приводят к неопределенному поведению (кроме, конечно же, mutable членов класса). И const_cast к этому не имеет никакого отношения.
Строка с=с+1 сначала помещает с в регистр, потом - делает SUM C, 1, потом - записывает куда-то результат
с+=1 - команда не требует возможности обращения к промежуточному результату и экономит 1 такт на перезаписи.
К языку С++ все эти разглагольствования не имею никакого отношения. Если c - это просто переменная, то c += 1 по определению эквивалентно c = c + 1.
#include - пишется в начале файла. Подключает так называемые библиотеки,
#include не подключает никакие библиотеки. #include включает стандартные заголовки или внешние заголовочные файлы. Это совсем другое.
<string> - модуль со строковым типом данных, которого в "чистом" С++ нету.
Небольшой экскурс. Строки, это на самом деле массивы символов, и чтоб не возникало возни с этим, создали модуль, который упрощает работу с ними. Если интересно как это устроенно на программном уровне, погугли "С-строки".
Но при при чем здесь C-строки? Заголовок <string> не имеет прямого отношения к С-строкам.
P.S. И, конечно же, проверка правописания и tsya.ru. Трудно читать.
Если быть педантичным, то += - это присваивание со сложением. Такие операторы называются операторами 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]; не хранит в себе никакие адреса. Все адреса, которые вы используете для адресной арифметики по такому массиву, вычисляются "на лету". Они не хранятся нигде в массиве.
Функция должна быть объявлена до ее использования. Это значит что все функции будут располагаться вверху файла.
Не ясно, что значит "функции будут располагаться".
А во-вторых, что существенно важнее и критичнее, в С++ выделение/освобождение "сырой" памяти изнутри new[]/delete[]-выражений выполняют потенциально замещаемые пользователем функции operator new[]/operator delete[]. Что за механизм распределения памяти будет скрываться за этими функциями, где и как он хранит размер выделенного блока и хранит ли вообще - это на уровне ядра языка не известно. По каковой причине никакого доступа к информации о размере блока у delete[]-выражения нет и быть не может.
В чем смысл этого примера? Все прекрасно поняли, что автор предыдущего комментария имел в виду предлагаемую реализацию, которая каким-то способом сохраняет признак "массив - не массив" при выделении памяти через new или new []. В такой реализации, разумеется, нет вообще никакой сложности в том, чтобы определить массив это или нет.
*35--Для синтаксического разбора регистров использовать объединения вкупе с битовымиполями.
Язык С сам по себе не поддерживает такого использования битовых полей. В спецификации битовых полей специально подчеркивается, что способ их размещения в памяти никак не оговаривается вообще. Подобное использование - implementation-dependent хак.
Битовые поля были введены в язык для экономии памяти, а не для разбора фиксированных битовых карт.
21–Не использовать операторы >, >= Вместо них использовать <, <= просто поменяв местами аргументы там, где это нужно. Это позволит интуитивно проще анализировать логику по коду. Человеку еще со времен школьной математики понятнее, когда то, что слева - то меньше, а то, что справа - то больше.
Очень спорный совет, опирающийся на формальную, а не не человеческую логику. В случаях, когда оба сравниваемых значения имеют одинаковый "приоритет" - да, это хорошая идея. Но в подавляющем большинстве случае роли сравниваемых значений ассиметричны: есть тот, "кого" мы сравниваем, и есть тот, "с кем" мы сравниваем. Это особенно выраженно когда первое значение постоянно меняется (напр., в процессе поиска), а последнее - более-менее фиксировано. Именно в таком порядке и стоит записывать сравнения: сначала "кого", потом "с кем". Такая запись намного лучше передает естественный ход человеческой мысли. Если в таком порядке записи придется использовать оператор >, то так тому и быть.
Во-первых, пример заезжен и ответ очевиден - битовое поле оказалось знаковым, то есть получило значение -1.
Во-вторых, в языке С (в отличие от С++) тип int не является синонимом signed int в таком контексте. Битовое поле может оказаться знаковым, а может и беззнаковым. Это определяется реализацией. То есть в зависимости от implementation-defined факторов данный assert может выстрелить, а может и не выстрелить.
Вы "пишете на С++ уже 9 лет", но до сих пор не имеете представления о том, что означает термин неопределенное поведение?
Нет никакого ответа на вопрос о том, "какой будет результат", так как в С++ не существует такой операции вычитания вообще. Попытки предсказания каких-то "результатов" свидетельствуют лишь о некомпетентности их совершающего.
Нет, конечно. Это прямое и буквальное толкование священного текста, который вы вообще пытаетесь игнорировать.
Никакой индукции тут нет и быть не может.
Вы почему-то не в курсе того простого факта, что главным источником неопределенного поведения (UB) являются не какие-то "области памяти", "адреса в указателях", "аллокаторы" и прочая белиберда. Все это - никому не нужное пионерское разглагольствование, которое не имеет никакого отношения к делу вообще.
Главным источником фактического "непредсказуемого поведения" всегда являлась и является внутренняя логика компилятора, который использует UB для выполнения оптимизаций. Как известно, эквивалентным определением неопределенного поведения является следующее:
Если компилятор сумеет обнаружить, что вы где-то вычитаете друг из друга два "левых" указателя, компилятор имеет право вообще вышвырнуть этот участок кода из программы. Или заменить результат вычитания на безусловный
0
. И или на42
. И он будет прав, ибо он имеет право полагать, что этот код никогда не будет выполняться.Или он может вообще отказаться транслировать ваш код.
Компиляторы уже давно это делают. Мы уже прошли и через strict overflow semantics, и через strict aliasing semantics, и через возвращение нулевых ссылок/указателей на локальные переменные в функции, и еще много других компиляторных решений, основанных на UB, но почему-то все равно определенным индивидуумам трудно понять, что UB - это в первую очередь последствия оптимизационных решений компилятора, а не следствие свойств их аппаратной архитектуры.
Если вы пытаетесь вычитать "левые" указатели, то язык С++ открытым тестом вам говорит - это не вычитание вообще. А ваши разглагольствования про "непрерывную память" - это пустопорожние разглагольствования, которые никакого отношения к вопросу не имеют.
Как правильно заметил KanuTaH выше, хотите произвольно обращаться с адресами - приводите указатели к
std::uintptr_t
и дальше делайте с результатами что угодно.Ну значит я изначально просто неправильно понял, что именно вы хотели сказать.
Временные объекты создаем мы сами при реализации перегруженных версий оператора постфиксного
++
. Но к встроенному оператору постфиксного++
, работающему со встроенными типами, все эти соображения неприменимы. Они создают неверную иллюзию того, что встроенный постфиксный++
будет выполнять какие лишние ненужные действия. Не будет.Я сам предпочитаю использовать префиксный
++
из соображений обобщенной оптимальности, если мне не нужен явно именно постфиксный. Но, тем не менее, я считаю, что не стоит ради этого заниматься дезинформацией новичков. До оптимизаций они еще успеют добраться.Из спецификации языков С и С++. Где ясно сказано, что вычитание указателей разрешено только для указателей на элементы одного и того же массива (или на воображаемый элемент после последнего).
В стандартном С++, как впрочем и в стандартном С, код
является некорректным, т.е. некомпилируемым. Все остальное - баги кривых компиляторов или, скорее, неправильная интерпретация диагностических сообщений пользователями.
С этим никто и не спорил. Имя массива - ни в коем случае не указатель на первый элемент. В rvalue-контекстах имя массива может лишь неявно конвертироваться к значению указателя на первый элемент. Именно это сказано в правиле "array type decay", которое я привел выше.
Не понимаю, зачем вы взялись городить весь этот огород с
(int *)
и&
(причем сразу с&
у вас не получилось и вы вынуждены были добавить приведение типа).То, о чем обычно ведут речь в таких случаях, выглядит так
И не надо никакого взятия адреса или насильного приведения типа.
Что такое "динамический массив" и при чем он тут?
Не на "динамический массив", а на указатель. Вся суть в том, что вы заменяете
a
на указатель. А уж на что он там будет указывать - на некий "динамический массив" или на дырку от бублика - роли не играет.Нет, конечно. Никакого "взятия адреса" тут не нужно. Это правило называется "array type decay" или "array-to-pointer conversion".
Это правило говорит, что значение типа массив
T [N]
может быть неявно преобразовано к типу указательT *
. Получающийся в результате указатель указывает на нулевой элемент массива. Вот и все.А при чем здесь вообще RAM? Новичок обычно работает с десктопной платформой: Linux, Windows и т.п. На таких платформах ваша программа будет работать с виртуальной памятью, а не с RAM. Доступа к RAM как таковой у вас нет. С RAM работает ОС и вас непосредственно к RAM она не подпустит.
В спецификациях префиксного и постфиксного
++
нет ни слова ни о каких "временных объектах". Зачем их сюда притягивать?Инициализация, разумеется, не имеет никакого отношения к "операции присваивания", но по-моему достаточно понятно, что автор и не хотел этого сказать.
Нет. Разность указателей
T *
в С и С++ зависит только от того, сколько элементов типаT
содержится между этими указателями. Адресная арифметика в этих языках специфицирована на высоком уровне и от устройства адресации процессора не зависит. Стоит заметить, что вычитать друг из друга в С и С++ разрешается только указатели на элементы одного и того же массива.Грубейше неверно!
Вы уже второй раз "прикладываете документацию", но при этом выдумываете то, чего в этой документации нет даже отдаленно. Выделенное вами утверждение об "evaluated only once" не имеет никакого отношения ни к какой "атомарности".
Я не увидел там такой далеко идущей категоричности.
Ничего подобного там не сказано. Документация по ссылке говорит именно то, что она должна говорить:
const_cast
снимает константность с путей доступа к объектам, то есть с указателей или ссылок. На этом все. Там ни слова не сказано о некоем появлении разрешения на модификацию константных объектов.Ваша "документация" нигде и никак не отменяет фундаментального правила С и С++: модификация константных объектов запрещена, т.е. приводит к неопределенному поведению. Пытаться модифицировать объект через путь доступа, полученный от каста, разрешается только если сам объект НЕконстантен.
Более того, по вашей же ссылке ясно сказано: "Modifying a const object through a non-const access path [...] results in undefined behavior".
Вроде бы - совершенно простой и логичный набор правил. Но почему-то он зачастую вызывает затруднения. Почему-то встречается верование, что
const_cast
позволяет модифицировать константные объекты (или даже "предназначен для этого") ...Ваша программа имеет неопределенное поведение. Она ничего не демонстрирует.
В С++ нет таких понятий как "8086", "без оптимизаций", "SUM и MOV".
И? Что вы там увидели? Утверждение "evaluated only once" имеет какое-то значение только если
c
- некое выражение с нетривиальным поведением и/или побочными эффектами. То есть, например, это гарантирует, что вf() += 1
функцияf
будет вызвана только один раз. Если жеc
- просто переменная, то вc += 1
выделенный вами текст не значит вообще ничего.Вы просто похитили и перелицевали известный анекдот: ... - "Хэнде хох!" - "Но это же немецкий..." - "А, ну значит еще немецкий знаю".
Вообще-то сделанное автором утверждение справедливо для всех практически используемых целочисленных знаковых представлений. Знаковый бит равен 1 - это отрицательное число и в прямом, и в обратном и в дополнительном коде (если считать "отрицательный ноль" отрицательным числом).
Это как это??? Никакой
const_cast
не позволит изменить значение константного объекта. В С++ вообще не существует возможности изменить константный объект, т.е. любые попытки это сделать приводят к неопределенному поведению (кроме, конечно же,mutable
членов класса). Иconst_cast
к этому не имеет никакого отношения.К языку С++ все эти разглагольствования не имею никакого отношения. Если
c
- это просто переменная, тоc += 1
по определению эквивалентноc = c + 1
.#include
не подключает никакие библиотеки.#include
включает стандартные заголовки или внешние заголовочные файлы. Это совсем другое.Но при при чем здесь C-строки? Заголовок
<string>
не имеет прямого отношения к С-строкам.P.S. И, конечно же, проверка правописания и tsya.ru. Трудно читать.
Если быть педантичным, то
+=
- это присваивание со сложением. Такие операторы называются операторами compund assignment и на них распространяются все правила операторов присваивания.Что за ерунду вы пишете? Это же классика С и C++ FAQ.
5 / 2
всегда равно 2 и никогда не дает "дробное число" в том смысле, что переменнаяa
получит значение 2.0, а не 2.5. Для целочисленных операндов/
- это операция целочисленного деления.В языке C++ тип данных
bool
принимает значенияfalse
илиtrue,
а не0
или1
. Это несколько иное.К чему здесь это оговорка про какой-то "первый байт"? Ссылка ссылается на переменную - этим все сказано.
Где это он записан в шестнадцатеричной системе? А если я запишу адрес в десятичной системе, то это будет уже не адрес?
Вы уже ввели в своей статье разделение на указатели и ссылки. Почему же вы продолжаете упорно называть указатели ссылками?
"Нетрудно догадаться"? Вы все время замечаете, что это "ссылка на первый байт элемента". Поэтому по здравой логике "догадаться" мы скорее всего должны, что
A+1
- это ссылка на второй байт, так? А это все таки ссылка на второй элемент. До этого непросто догадаться из-за ваших странных (и ненужных) уточнений про "первый байт".Что? Массив
int D[5][5];
не хранит в себе никакие адреса. Все адреса, которые вы используете для адресной арифметики по такому массиву, вычисляются "на лету". Они не хранятся нигде в массиве.Не ясно, что значит "функции будут располагаться".
... Это во-первых,
А во-вторых, что существенно важнее и критичнее, в С++ выделение/освобождение "сырой" памяти изнутри
new[]/delete[]
-выражений выполняют потенциально замещаемые пользователем функцииoperator new[]/operator delete[]
. Что за механизм распределения памяти будет скрываться за этими функциями, где и как он хранит размер выделенного блока и хранит ли вообще - это на уровне ядра языка не известно. По каковой причине никакого доступа к информации о размере блока уdelete[]
-выражения нет и быть не может.В чем смысл этого примера? Все прекрасно поняли, что автор предыдущего комментария имел в виду предлагаемую реализацию, которая каким-то способом сохраняет признак "массив - не массив" при выделении памяти через
new
илиnew []
. В такой реализации, разумеется, нет вообще никакой сложности в том, чтобы определить массив это или нет.Язык С сам по себе не поддерживает такого использования битовых полей. В спецификации битовых полей специально подчеркивается, что способ их размещения в памяти никак не оговаривается вообще. Подобное использование - implementation-dependent хак.
Битовые поля были введены в язык для экономии памяти, а не для разбора фиксированных битовых карт.
Очень спорный совет, опирающийся на формальную, а не не человеческую логику. В случаях, когда оба сравниваемых значения имеют одинаковый "приоритет" - да, это хорошая идея. Но в подавляющем большинстве случае роли сравниваемых значений ассиметричны: есть тот, "кого" мы сравниваем, и есть тот, "с кем" мы сравниваем. Это особенно выраженно когда первое значение постоянно меняется (напр., в процессе поиска), а последнее - более-менее фиксировано. Именно в таком порядке и стоит записывать сравнения: сначала "кого", потом "с кем". Такая запись намного лучше передает естественный ход человеческой мысли. Если в таком порядке записи придется использовать оператор
>
, то так тому и быть."Догадался"?
Во-первых, пример заезжен и ответ очевиден - битовое поле оказалось знаковым, то есть получило значение
-1
.Во-вторых, в языке С (в отличие от С++) тип
int
не является синонимомsigned int
в таком контексте. Битовое поле может оказаться знаковым, а может и беззнаковым. Это определяется реализацией. То есть в зависимости от implementation-defined факторов данный assert может выстрелить, а может и не выстрелить.