64-разрядное число не помещается в регистр в режиме x86_64 - PullRequest
2 голосов
/ 11 марта 2019

У меня на компьютере есть процессор Intel, работающий в 64-битном режиме, x86_64, где регистры имеют размер 64 бита, если я использую регистр слов или использую некоторую оптимизацию флагов, в которую обычно помещается переменнаярегистр, но если я поставлю значение выше 32 бит, компилятор жалуется, то есть мой int не 64-битный, почему это происходит?Это не должно было быть 64 бита, если бы переменная была помещена в регистр, а я даже не получил ее адрес памяти?То есть он даже не помещается в стек.

#include <stdio.h>

int main(void) {

    register int x = 4294967296;

    return 0;
}

Компиляция:

gcc example.c -o пример -Wall -Wextra

Вывод:

предупреждение: переполнение при неявном преобразовании константы [-Woverflow] регистр int x = 4294967296;

Ответы [ 3 ]

8 голосов
/ 11 марта 2019

Ключевое слово C register не влияет на ширину выбранного вами типа C.int - это 32-разрядный тип во всех 32- и 64-разрядных интерфейсах x86 ABI, включая x86-64 System V и Windows x64.(long также 32-битный в Windows x64, но 64-битный в Linux / Mac / все остальное x86-64 1 .)

A register intпо-прежнему int, с учетом всех ограничений INT_MAX, INT_MIN, а переполнение со знаком является неопределенным поведением. Это не превращает ваш источник C в переносимый язык ассемблера.

Использование register просто говорит компилятору, что даже в режиме отладки можно свободно хранить переменную (младшая половинаof) регистр.


Если регистр имеет 64 бита, почему условное обозначение int должно быть 32 бита

int равно 32-бит, потому что никто не хочет, чтобы массив int становился 64-битным на 64-битной машине, имея удвоенную площадь кеша.

Я не знаю ни одной реализации C, где int = int64_t, даже вне x86-64.Даже DEC Alpha (который был агрессивно 64-битным и разработан с нуля для 64-битных), я думаю, все еще использовал 32-битный int.

Создание int 32-битных при увеличении с 16 до 32-битных машин имеет смысл, потому что 16-битные "слишком малы" иногда.(Но помните, что ISO C гарантирует, что int составляет не менее 16 бит. Если вам нужно больше, в действительно переносимой программе лучше использовать long или int_least32_t.)

Но 32 бита "достаточно велико" для большинства программ, и 64-битные машины всегда имеют быстрые 32-битные целые числа, поэтому int остается 32-битным при переходе с 32-битных 64-битных машин.

На некоторых машинах 16-битные целые числа не очень хорошо поддерживаются.например, реализация преобразования в 16 битов с uint16_t в MIPS потребует дополнительных непосредственных инструкций AND.Поэтому сделать int 16-битным типом было бы плохим выбором.

На x86 вы можете просто использовать 16-битный размер операнда и использовать movzx вместо mov при копировании, ноэто нормально для int быть 32-битным на 32-битных машинах, поэтому все 32-битные ABI x86 выбрали это.

Когда ISA были расширены с 32 до 64-битных, былопричина нулевой производительности сделать int шире , в отличие от случая 16-> 32.(Также в этом случае short оставался 16-разрядным, поэтому существовало точное имя для 16- и 32-разрядных целых чисел, даже до появления C99 stdint.h).

В x86-64, операнде по умолчаниюразмер по-прежнему 32-битный;mov rax, rcx занимает дополнительный байт префикса (REX.W) по сравнению с mov eax, ecx, поэтому 32-битный немного более эффективен.Кроме того, 64-разрядное умножение было немного медленнее на некоторых процессорах, а 64-разрядное деление значительно медленнее, чем 32-разрядное, даже на современных процессорах Intel. Преимущества использования 32-битных регистров / инструкций в x86-64


Кроме того, компиляторам нужен примитивный тип для int32_t, если они хотят предоставить дополнительный int32_t ввсе.(Типы дополнения с фиксированной шириной 2 являются необязательными, в отличие от int_least32_t и т. Д., Которые, как гарантируют, не будут дополнением 2 или свободны от дополнения.)

Компиляторы с 16-разрядными short и 64-bit int может иметь имя типа, специфичное для реализации, например __int32, которое они используют в качестве typedef для int32_t / uint32_t, поэтому этот аргумент не является полным showtopper.Но это было бы странно.

При увеличении с 16 до 32 имеет смысл изменить int на более широкий, чем минимум ISO C, потому что у вас все еще есть short в качестве имени для 16-битного,(Этот аргумент не очень хорош, потому что у вас есть long в качестве имени для 32-разрядных целых чисел в 32-разрядных системах.)

Но при переходе на 64-разрядную версию вы хотите *Тип 1090 * some должен быть 32-битным целочисленным типом.(И long не может быть уже int).char / short / int / long (или long long) охватывает все 4 возможных размера операндов. int32_t не гарантированно будет доступно во всех системах, поэтому ожидаем, что всеиспользуйте это, если они хотят, чтобы 32-разрядные целые числа со знаком не были приемлемыми для переносимого кода.


Сноска 1 :

Вы можете в любом случае утверждать, лучше ли для long быть 32- или 64-битным типом.Решение Microsoft оставить его 32-битным означало, что структурные макеты, использующие long, могут не меняться между 32-битным и 64-битным кодом (но они могли бы, если бы они включали указатели).

ISO C требует, чтобы long былопо крайней мере, 32-битный тип (на самом деле они определяют его в терминах минимального и максимального значений, которые могут быть представлены, но в других местах они требуют, чтобы целочисленные типы были двоичными целыми числами с необязательным заполнением).

В любом случае, некоторыекод использует long, потому что ему нужен 32-битный тип, но ему не нужен 64;во многих случаях больше битов не лучше, они просто не нужны.

В пределах одного ABI, такого как x86-64 System V, всегда удобно иметь длину long равной указателю,но так как переносимый код всегда должен использовать unsigned long long или uint64_t или uint_least64_t или uintptr_t в зависимости от варианта использования, для System v x86-64 было бы ошибкой выбрать 64-битную длину.

OTOH, более широкие типы для локальных пользователей могут иногда сохранять инструкции, избегая расширения знака при индексировании указателя, но тот факт, что переполнение со знаком является неопределенным поведением, часто позволяет компиляторам расширять int в asm, когда это удобно.

2 голосов
/ 11 марта 2019

Ключевое слово register здесь неактуально; тип данных int остается 32-битным на рассматриваемой платформе.

#include <stdio.h>
#include <stdint.h>

int main(void) 
{
    int64_t x = 4294967296;
    return 0;
}

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

1 голос
/ 11 марта 2019

На вашей платформе int, вероятно, 32 бит.

Вы просите, чтобы компилятор поместил значение (4294967296 = 0x100000000), которое не может поместиться в 32 бита, в регистр.

register int x = 4294967296;

Но в любом случае, даже если вы удалите ключевое слово register, компилятор все равно будет жаловаться по той же причине.

...