#c# #.net #intptr
#c# #.net #intptr
Вопрос:
Это простой на вид вопрос:
Учитывая, что целые числа собственного размера лучше всего подходят для арифметики, почему C # (или любой другой язык .NET) не поддерживает арифметику с помощью IntPtr
и UIntPtr
собственного размера?
В идеале вы могли бы написать код, подобный:
for (IntPtr i = 1; i < arr.Length; i = 2) //arr.Length should also return IntPtr
{
arr[i - 1] = arr[i]; //something random like this
}
чтобы это работало как на 32-разрядных, так и на 64-разрядных платформах. (В настоящее время вам приходится использовать long
.)
Редактировать:
Я не использую их в качестве указателей (слово «указатель» даже не упоминалось)! Их можно просто рассматривать как C # аналог native int
в MSIL и intptr_t
в C stdint.h
, которые являются целыми числами, не указателями.
Комментарии:
1. @BoltClock: Да … как ты думаешь, что такое . ЧИСТЫЙ возвращаемый тип метода, который возвращает
native int
?
Ответ №1:
В .NET 4 IntPtr
поддерживается арифметика между левым операндом типа int
и правым операндом целочисленных типов ( long
, и т.д.).
[Редактировать]: Как говорили другие пользователи, они предназначены для представления указателей на родных языках (что подразумевается под названием IntPtr). Можно утверждать, что вы используете их как собственные целые числа, а не указатели, но вы не можете упускать из виду, что одна из основных причин, по которой собственный размер целого числа когда-либо имеет значение, заключается в использовании в качестве указателя. Если вы выполняете математические операции или другие общие функции, независимые от архитектуры процессора и памяти, на которых выполняется ваш код, возможно, более полезно и интуитивно понятно использовать такие типы, как int
и long
, где вы знаете их фиксированный размер и верхние и нижние границы в любой ситуации, независимо от аппаратного обеспечения.
Точно так же, как тип IntPtr
предназначен для представления собственного указателя, арифметические операции предназначены для представления логических математических операций, которые вы бы выполнили с указателем: добавление некоторого целочисленного смещения к собственному указателю для получения нового собственного указателя (не то чтобы добавление двух IntPtr
символов не поддерживалось и не использовалось IntPtr
в качестве правого операнда).
Комментарии:
1. 1 Я этого не знал!! = O Но я имел в виду полное использование
IntPtr
, а не добавление еще одного целого числа фиксированного размера в микс.2. Обновлено моими мыслями о том, почему использование целого числа фиксированного размера подходит для данной ситуации
3. @jeffora: Я понимаю, вы в основном говорите, что нет ситуации, в которой это было бы полезно; верно? Хотя мне было бы трудно это проглотить, это кажется очень правдоподобным объяснением. 🙂
4. Поправьте меня, если я ошибаюсь, но size_t должен представлять размер объекта, а его размер зависит от платформы. IntPtr — это целочисленный тип с собственным размером, но, согласно MSDN, это «тип, зависящий от платформы, который используется для представления указателя или дескриптора». ( msdn.microsoft.com/en-us/library/system.intptr.aspx ), то есть указатель или дескриптор, а не просто любой произвольный размер платформы int
5. Конечно, вы можете использовать его как любой int произвольного размера для платформы, но, возможно, не ожидайте, что он будет поддерживать операции, которые не имеют смысла для конкретных представлений, для которых он предназначен.
Ответ №2:
Возможно, целые числа собственного размера обеспечивают самую быструю арифметику, но они определенно не подходят для большинства программ без ошибок.
Лично я ненавижу программирование с целочисленными типами, размеры которых я не знаю, когда сажусь, чтобы начать печатать (я смотрю на вас, C ), и я определенно предпочитаю спокойствие, которое дают типы CLR, очень сомнительному и, безусловно, условному выигрышу в производительности, который может предложить использование инструкций процессора, адаптированных к платформе.
Учтите также, что JIT-компилятор может оптимизировать архитектуру, на которой выполняется процесс, в отличие от «обычного» компилятора, который должен генерировать машинный код, не имея доступа к этой информации. Следовательно, JIT-компилятор может генерировать код так же быстро, потому что он знает больше.
Я полагаю, что я не одинок в этом мнении, так что это может иметь значение по какой-то причине.
Комментарии:
1. Теперь это правдоподобное объяснение (в отличие от ответа «указатели небезопасны!», который совершенно не имеет отношения к делу). 🙂 Итак, теперь мой вопрос к вам таков: как вы должны взаимодействовать со структурой, в которой есть
size_t
поле, содержащее, скажем, индекс в некотором массиве? (И 1 для C -bash …)2. @Mehrdad: Во-первых, я только что добавил третий абзац; прочтите и его.
3. Я прочитал ваш третий абзац прямо сейчас, но это не просто проблема эффективности: это проблема корректности , например, для итерации по массивам, которые могут иметь произвольные размеры, независимым от платформы образом.
4. @Mehrdad: О
size_t
: я мало что знаю о внутренних компонентах взаимодействия. Если вам нужно отобразитьsize_t
поле того же размера, то технически взаимодействие в общем случае невозможно, потому что размер неизвестен; вы можете взаимодействовать только с определенным двоичным файлом, размер которогоsize_t
был жестко запрограммирован компилятором. Если маршалинг является только односторонним и маршаллер может преобразовывать меньшие типы в более крупные, тогда вы можете маршалироватьsize_t
вunsigned long
и все будет в порядке, пока мы не получим 128-битные архитектуры.5. Да, точно — но тогда вы бы умерли на 128-битных архитектурах. Не с этой ли проблемой столкнулся 32-разрядный код при переходе на 64-разрядный?
Ответ №3:
На самом деле я могу назвать одну причину, по которой IntPtr (или UIntPtr) был бы полезен: для доступа к элементам массива требуются целые числа собственного размера. Хотя собственные целые числа никогда не предоставляются программисту, они внутренне используются в IL. Что-то подобное some_array[index]
в C # фактически скомпилируется до some_array[(int)checked((IntPtr)index)]
в IL. Я заметил это после дизассемблирования моего собственного кода с помощью ILSpy. ( index
Переменная в моем коде 64-разрядная.) Чтобы убедиться, что дизассемблер не допустил ошибки, собственный инструмент ILDASM от Microsoft показывает наличие conv.u
и conv.i
инструкций в моей сборке. Эти инструкции преобразуют целые числа в собственное представление системы. Я не знаю, как влияет на производительность наличие всех этих инструкций преобразования в IL-коде, но, надеюсь, JIT достаточно умен, чтобы оптимизировать снижение производительности; если нет, то следующая лучшая вещь — разрешить манипулировать собственными целыми числами без преобразований (что, на мой взгляд, может быть основной мотивацией для использования собственного типа).
В настоящее время язык F # допускает использование nativeint
и и его аналога без знака для арифметики. Однако массивы могут быть проиндексированы только с помощью int
в F #, что nativeint
означает, что это не очень полезно для целей индексации массивов.
Если это действительно вас так беспокоит, напишите свой собственный компилятор, который снимает ограничения на использование собственных целых чисел, создайте свой собственный язык, напишите свой код на IL или настройте IL после компиляции. Лично я думаю, что это плохая идея выжимать дополнительную производительность или экономить память, используя собственный int. Если вы хотите, чтобы ваш код идеально подходил системе, вам лучше всего использовать язык более низкого уровня с поддержкой встроенных функций процессора.
Ответ №4:
.Net Framework пытается не вводить операции, которые невозможно объяснить. Т.е. нет DateTime DateTime, потому что нет такого понятия, как сумма двух дат. То же самое рассуждение применимо к типам указателей — нет понятия суммы двух указателей. Тот факт, что IntPtr хранится как зависящее от платформы значение int, на самом деле не имеет значения — существует множество других типов, которые хранятся внутри как базовые значения (опять же, DateTime может быть представлен как long).
Комментарии:
1. Вопреки тому, что вы упомянули, на самом деле оно не «хранится» как не зависящее от платформы целое число (оно хранится как
void*
), но оно является не зависящим от платформы целым числом, потому что это то, чтоnative int
отображается в MSIL. Я ничего не говорю о механизме хранения, только о семантике.
Ответ №5:
Потому что это не «безопасный» способ обработки адресации памяти. Арифметика указателей может привести ко всевозможным ошибкам и проблемам с адресацией памяти, которых C # явно призван избегать.
Комментарии:
1. Это не одно и то же. C — нет .NET. .NET — это управляемая среда. Указатель не является фиксированным значением в управляемом мире. У вас есть ссылки , которые содержат значения, используемые . NET framework для поиска объектов в памяти. Их фактическое местоположение может быть перемещено GC в любое время. Вот почему указатели небезопасны и играть с ними сложно.
2. @Paul: Это не имеет никакого отношения к указателям! Вы случайно не знакомы с
native int
типом данных MSIL? Это полностью отличается отvoid*
…3. Да, я очень хорошо знаком с внутренней работой JIT и тем, как он на самом деле генерирует код. IntPtr — это специальный тип для хранения указателей . Вы пытаетесь использовать это для чего-то другого. Тип IntPtr разработан для обеспечения безопасного способа хранения указателя в управляемой среде. Это не произвольное целое значение.
4. @Paul: Когда метод возвращает
native int
в IL, какой тип данных возвращаемого типа вы видите, когда размышляете над ним? И какоеnative int
отношение это имеет к указателям?5. Какое это имеет отношение к вашему вопросу? Возвращаете ли вы int, ref, InPtr или собственный int, когда он JITted, все это сохраняется в eax / rax — удивите регистром собственного размера.