Короче говоря, объекты Java обладают некоторыми очень своеобразными свойствами.
В общем, Java имеет примитивные типы (int
, bool
, char
, double
и т. д.), которые передаются непосредственно по значению. Тогда у Java есть объекты (все, что происходит от java.lang.Object
). Объекты на самом деле всегда обрабатываются посредством ссылки (ссылка является указателем, который вы не можете коснуться). Это означает, что по сути объекты передаются по ссылке, так как ссылки обычно не интересны. Тем не менее, это означает, что вы не можете изменить, на какой объект указывается, поскольку сама ссылка передается по значению.
Звучит ли это странно и запутанно? Рассмотрим, как C реализует передачу по ссылке и передает значение. В C по умолчанию принято передать значение. void foo(int x)
передает значение int по значению. void foo(int *x)
- это функция, которая не хочет int a
, а указатель на int: foo(&a)
. Можно было бы использовать это с оператором &
для передачи адреса переменной.
Возьмите это на C ++, и у нас есть ссылки. Ссылки в основном (в этом контексте) синтаксического сахара, которые скрывают указательную часть уравнения: void foo(int &x)
вызывается foo(a)
, где сам компилятор знает, что это ссылка и адрес без ссылки a
должен быть принят. В Java все переменные, относящиеся к объектам, фактически относятся к ссылочному типу, фактически вызывая вызов по ссылке для большинства целей и целей без мелкозернистого управления (и сложности), предоставляемого, например, C ++.
Если мы посмотрим на Обоснование для языков международного стандартного программирования-C в разделе 6.3.1.8
Обычные арифметические преобразования , в нем говорится ( акцент мой в будущее ):
Правила в Стандарте для этих преобразований являются небольшими изменениями в K & amp; R: модификации соответствуют добавленным типам и правилам сохранения значения. Явная лицензия была добавлена для выполнения вычислений в «более широком» типе, чем это абсолютно необходимо, поскольку иногда это может приводить к меньшему и более быстрому коду, не говоря уже о правильном ответе более часто. Вычисления также могут выполняться в «более узком» типе с помощью правила «как если бы», пока тот же конечный результат получен. Явное литье всегда можно использовать для получения значения в желаемом типе
Раздел
6.3.1.8
из черновика стандарта C99 охватывает Обычные арифметические преобразования , который применяется к операндам арифметических выражений, например, в разделе6.5.6
Аддитивные операторы говорят:Если оба операнда имеют арифметический тип, обычные арифметические преобразования выполненных над ними.
Мы находим аналогичный текст в разделе
6.5.5
Мультипликативные операторы . В случае операнда short сначала целочисленные рекламные акции применяются из раздела6.3.1.1
Boolean, characters и integers , который гласит:Если int может представлять все значения исходного типа, значение преобразуется в int; в противном случае он преобразуется в unsigned int. Они называются целыми рекламными акциями.48) Все остальные типы не изменяются целыми рекламными акциями.
Обсуждение из раздела
6.3.1.1
Обоснование или Международные языки стандартного программирования - C в целых рекламных акциях на самом деле интереснее, я собираюсь выборочно указывать b / c, что слишком долго, чтобы полностью указать:Реализации были два основных лагеря, которые могут быть охарактеризованы как сохранение без знака и сохранение значений.
[...]
Подход без знака предполагает использование двух меньших неподписанных типов для unsigned int. Это простое правило и дает тип, который не зависит от среды исполнения.
Подход сохранения значения требует продвижения этих типов к подписанному int, если этот тип может правильно представлять все значения исходного типа, и в противном случае для продвижения этих типов к unsigned int. Таким образом, если среда выполнения представляет короткий, чем нечто меньшее, чем int, unsigned short становится int; в противном случае он становится беззнаковым int.
Это может иметь некоторые довольно неожиданные результаты в некоторых случаях как . Непоследовательное поведение неявного преобразования между неподписанными и более крупными подписанными типами демонстрирует, есть таких примеров больше. Хотя в большинстве случаев это приводит к работам, которые ожидаются.
float
, short
и char
рассматриваются стандартным типом «типов хранения», то есть субдиапазонами, которые вы можете использовать для сохранения некоторого пространства, но которые не собираются покупать вам какую-либо скорость, потому что их размер «неестественен» для CPU.
На некоторых процессорах это неверно, но хорошие компиляторы достаточно умны, чтобы заметить, что если вы, например, добавьте константу в unsigned char и сохраните результат обратно в unsigned char, тогда нет необходимости проходить через преобразование unsigned char -> int
. Например, с g ++ код, сгенерированный для внутреннего цикла
void incbuf(unsigned char *buf, int size) {
for (int i=0; i<size; i++) {
buf[i] = buf[i] + 1;
}
}
, является просто
.L3:
addb $1, (%rdi,%rax)
addq $1, %rax
cmpl %eax, %esi
jg .L3
.L1:
, где вы можете видеть, что команда беззнакового добавления сложения (addb
) Используется.
То же самое происходит, если вы выполняете вычисления между короткими int и сохраняете результат в коротких int.
Связанный вопрос, похоже, очень хорошо его охватывает: CPU просто нет. 32-разрядный процессор имеет свои собственные арифметические операции, настроенные для 32-разрядных регистров. Процессор предпочитает работать в своем любимом размере, и для таких операций копирование небольшого значения в регистр собственного размера дешево. (Для архитектуры x86 32-разрядные регистры названы так, как если бы они были расширенными версиями 16-разрядных регистров (eax
- ax
, ebx
- bx
и т. Д.), См. x86 целочисленные инструкции ).
Для некоторых чрезвычайно распространенных операций, в частности векторной / float-арифметики, могут быть специализированные инструкции, которые работают с другим типом или размером регистра. Для чего-то вроде короткого, заполнение (до) 16 бит нулей имеет очень небольшую стоимость исполнения и добавление специализированных инструкций, вероятно, не стоит времени или места на уме (если вы хотите получить действительно физическое о том, почему, я не уверен, что они будут занимать реальное пространство, но он становится более сложным).
Это не особенность языка, так как это ограничение физических процессорных архитектур, на которых выполняется код. Экран int
в C обычно является размером вашего стандартного регистра CPU. Больше кремния занимает больше места и больше мощности, поэтому во многих случаях арифметику можно выполнять только по типам «натурального размера». Это не универсально, но большинство архитектур все еще имеют это ограничение. Другими словами, при добавлении двух 8-битных чисел то, что на самом деле происходит в процессоре, это тип 32-разрядной арифметики, за которой следует либо простая битовая маска, либо другое подходящее преобразование типа.
int
, выражение будет вести себя так, как если бы его операнды были также принудительно и операция выполненных на меньшем типе. Нет определенных случаев, которые противоречат такому правилу, но некоторые компиляторы могут использовать продвижение в качестве оправдания, чтобы сделать вывод, что выражение типаx*=y;
(с обеими переменнымиunsigned short
) обещает, чтоx
не может превышать 2147483648 / y. – supercat 12 September 2015 в 18:11int x = 1234
иchar *y = &x
. Двоичное представление1234
-00000000 00000000 00000100 11010010
. Моя машина немного ориентирована, поэтому она меняет ее и сохраняет в памяти11010010 00000100 00000000 00000000
LSB приходит первым. Теперь основная часть. если я используюprintf("%d" , *p)
.printf
будет читать первый байт11010010
, только выход-46
, но11010010
-210
, поэтому зачем он печатает-46
. Я действительно смущен, я думаю, что какой-то символ для целостного продвижения делает что-то, но я не знаю. – Suraj Jain 17 August 2016 в 10:24