Сдвиг разряда C#: это поведение в спецификации, ошибка, или случайна?

Я работал с операторами сдвига разряда (см. мое Равенство Битового массива вопроса), и, ТАКИМ ОБРАЗОМ, пользователь указал на ошибку в моем вычислении моего операнда сдвига - я вычислял диапазон [1,32] вместо [0,31] для интервала (Ура для ТАКИМ ОБРАЗОМ сообщество!)

В решении проблемы я был удивлен найти следующее поведение:

-1 << 32 == -1

На самом деле это казалось бы этим n << s компилируется (или интерпретируется CLR - я не проверял IL), как n << s % bs(n) где бакалавр наук (n) = размер, в битах, n.

Я ожидал бы:

-1 << 32 == 0

Казалось бы, что компилятор понимает, что Вы смещаетесь вне размера цели и исправляете свою ошибку.

Это - просто академический вопрос, но кто-либо знает, определяется ли это в спецификации (я ничего не мог бы найти в 7.8 Операторах сдвига), просто случайный факт неопределенного поведения, или есть ли случай, где это могло бы произвести ошибку?

5
задан Community 23 May 2017 в 10:32
поделиться

1 ответ

Добавить к прениям здесь.

Известны проблемы с вывозом мусора, и понимание их помогает понять, почему их нет в C++.

1. Производительность?

Первая жалоба часто касается производительности, но большинство людей на самом деле не понимают, о чем они говорят. Как проиллюстрировано Мартином Беккетом , проблемой может быть не производительность как таковая, а предсказуемость производительности.

В настоящее время существует 2 семейства GC, которые широко развернуты:

  • Mark-And-Sweep вид
  • Reference-Counting вид

Mark And Sweep быстрее (меньшее влияние на общую производительность), но он страдает от синдрома «заморозить мир»: т.е. когда GC ударяется, все остальное останавливается до тех пор, пока G Если вы хотите построить сервер, который отвечает за несколько миллисекунд... некоторые транзакции не оправдают ваших ожиданий:)

Проблема Подсчет ссылок отличается: подсчет ссылок добавляет накладные расходы, особенно в средах Multi-Threading, потому что требуется атомарный подсчет. Кроме того, существует проблема эталонных циклов, поэтому вам нужен умный алгоритм, чтобы обнаружить эти циклы и устранить их (как правило, реализовать «заморозить мир» тоже, хотя и менее часто). В целом, на сегодняшний день этот тип (даже если обычно он более чувствителен или, скорее, реже замерзает) медленнее, чем Mark And Sweep .

Я видел статью реализаторов Eiffel, которые пытались реализовать Отсчет Сборщик мусора, который имел бы сходную глобальную производительность с Mark And Sweep без аспекта «Заморозить мир». Требовалась отдельная резьба для GC (типовая). Алгоритм был немного пугающим (в конце) но в статье хорошо поработали над введением понятий по одному и показав эволюцию алгоритма от «простой» версии к полноценной. Рекомендуется читать, если только я могу положить руки обратно в файл PDF...

2. Acquisition Is Initialization (RAI)

В C++ обычно используется идиома, в соответствии с которой права собственности на ресурсы переносятся в объект для обеспечения их надлежащего освобождения. Он в основном используется для памяти, так как у нас нет сбора мусора, но он также полезен, тем не менее, для многих других ситуаций:

  • Блокировки (многопотоковые, дескриптор файла,...)
  • подключения (к базе данных, другому серверу,...)

Идея состоит в правильном управлении временем жизни объекта:

  • Он должен быть жив, пока он вам нужен
  • он должен быть убит, когда вы закончите с ним

Проблема ГК в том, что если он помогает с первым и в конечном итоге гарантирует, что позже... этого «предельного» может быть недостаточно. Если вы разблокируете блокировку, вам бы очень хотелось, чтобы она была разблокирована сейчас, чтобы она не блокировала дальнейшие вызовы!

Языки с GC имеют два порядка работы:

  • не используйте GC при достаточном распределении стека:это обычно для проблем производительности, но в нашем случае это действительно помогает, поскольку область определяет время жизни
  • с помощью конструкции ... но это явный (слабый) RAI, в то время как в C++ RAI неявный, так что пользователь НЕ МОЖЕТ невольно совершить ошибку (опустив с помощью ключевого слова )

3. Смарт-указатели

Смарт-указатели часто отображаются как серебряный маркер для обработки памяти в C++ . Часто я слышал: нам ведь не нужен ГК, так как у нас есть умные указатели.

Нельзя больше ошибаться.

Умные указатели помогают: auto _ ptr и unique _ ptr используют концепции RAI, что действительно очень полезно. Они настолько просты, что вы можете написать их сами довольно легко.

Когда нужно разделить владение, это становится сложнее: вы можете поделиться между несколькими потоками, и есть несколько тонких проблем с обработкой подсчета. Поэтому, естественно, следует к shared _ ptr .

Это здорово, это то, что Boost в конце концов, но это не серебряная пуля. На самом деле, основная проблема с shared _ ptr состоит в том, что он эмулирует GC, реализованный Ссылочным подсчетом , но вы должны реализовать обнаружение цикла самостоятельно... Urg

Конечно, есть эта слабая _ ptr вещь, но я, к сожалению, уже видел утечки памяти, несмотря на использование shared _ ptr из-за этих циклов... и когда вы находитесь в многопоточной среде, это очень трудно обнаружить!

4. Какое решение?

Нет серебряной пули, но, как всегда, это определенно осуществимо. В отсутствие GC необходимо четко определить право собственности:

  • предпочитает иметь одного владельца в данный момент времени, если это возможно
  • , если нет, убедиться, что схема вашего класса не имеет никакого цикла, относящегося к собственности, и сломать их с тонким применением слабого _ ptr

Так что было бы здорово иметь GC... однако это не тривиальная проблема. И в то же время нам нужно просто засучить рукава.

-121--569529-

Указатели и ссылки похожи, но различаются несколькими способами:

  1. Они имеют разный синтаксис доступа. При наличии T * a и T & b доступ к переменным и функциям элемента осуществляется с помощью a- > member и b.member соответственно.
  2. Указатели не могут указывать ни на что, в то время как ссылки всегда должны указывать на что-либо. a = 0 является законным, но b = 0 или что-либо в этом отношении не является.
  3. Указатели могут быть "переустановлены" (т.е. указатель может быть изменен), тогда как ссылки не могут. a = & b является законным, но int c; b = c; - нет ( T & b = c - ссылки могут быть установлены только при их инициализации). (Спасибо Майк Д.)
  4. Указатели не могут ссылаться на временные, но const ссылки могут.
  5. Указатели могут указывать на другие указатели ( T * * )но ссылки не могут ссылаться на другие ссылки (то есть нет такой вещи, как T & & , хотя обратите внимание, что C++ 0x будет использовать T & & для определения семантики перемещения). В результате массивы ссылок будут недоступны. (Спасибо AshleysBrain)

Можно задаться вопросом, почему у нас вообще есть ссылки, а не только постоянно использовать указатели (как в C). Причина - перегрузка оператора или определенные операторы.

Рассмотрим оператор назначения. Какой будет синтаксис функции без ссылок? Если бы это был T * оператор = (T * lhs, T rhs) , то нам пришлось бы писать такие вещи, как:

int a(1);
&a = 2;

По сути, ссылки позволяют нам иметь функции l-значений без необходимости ссылки указателя и отмены синтаксиса.

-121--4553952-

Я считаю, что здесь находится соответствующая часть спецификации:

Для предопределенных операторов число битов для сдвига вычисляется следующим образом:

  • Когда тип x равен int или uint, число сдвигов задается значением младшие пять битов счетчика. Другими словами, вычисляется число сдвигов из числа & 0x1F.

  • Если тип x является длинным или ulong, число сдвигов задается шесть битов подсчета низкого порядка. Другими словами, вычисляется число сдвигов из числа & 0x3F.

Если результирующее число сдвигов равно нулю, операторы сдвига просто возвращают значение x.

Значение 32 равно 0x20 . Выражение 0x20 & 0x1F вычисляется как 0 . Следовательно, число сдвигов равно нулю, и сдвиг не выполняется; выражение -1 < < 32 (или любое x < < 32 ) возвращает только исходное значение.

9
ответ дан 14 December 2019 в 01:06
поделиться
Другие вопросы по тегам:

Похожие вопросы: