Создание кода, 64-разрядного чистый

Прежде чем Вы начнете обновлять свой код, необходимо ознакомить себя с документом Технологический Обзор Mac. После чтения того документа первая вещь, которую необходимо сделать, скомпилировать код с -Wall флаг компилятора и фиксирует любые происходящие предупреждения. В частности удостоверьтесь, что все прототипы функции находятся в объеме, потому что прототипы из объема могут скрыть много тонких проблем мобильности.

На высоком уровне, для создания кода 64-разрядным чистый необходимо сделать следующее:

Общие подсказки по программированию

Этот раздел содержит некоторые общие советы для того, чтобы сделать Ваш код 64-разрядным чистый.

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

Если Вы не включаете код языка встроенного ассемблерного кода, необходимо обычно тестировать на присутствие нейтральных архитектурой макросов такой как __LITTLE_ENDIAN__ или __LP64__ вместо того, чтобы тестировать на определенную архитектуру процессора.

Макрос __LP64__ может использоваться для тестирования на компиляцию LP64 архитектурно-независимым способом. Например:

#ifdef __LP64__
// 64-bit code
#else
// 32-bit code
#endif

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

Например, код перенесся с #ifdef __i386__ директива не будет включена при компиляции для x86_64 архитектура. Следующее перечисление (Перечисление 3-1) дает примеры как к тестам записи на различную архитектуру.

  Изменения определения Архитектуры перечисления 3-1

#ifdef __ppc__
// 32-bit PowerPC code
#else
#ifdef __ppc64__
// 64-bit PowerPC code
#else
#if defined(__i386__) || defined(__x86_64__)
// 32-bit or 64-bit Intel code
#else
#error UNKNOWN ARCHITECTURE
#endif
#endif
#endif

Код, ищущий только __ppc__ или __i386__ если Вы скомпилируете для связанной 64-разрядной архитектуры, определение повредится.

Для кода, определенного для (немежплатформенного) OS X, TargetConditionals.h заголовок также предоставляет макро-поддержку для архитектурно-зависимого кода. Например:

#include <TargetConditionals.h>
 
#if TARGET_RT_LITTLE_ENDIAN
    ...
#elif TARGET_RT_BIG_ENDIAN
    ...
#else
    #error Something is very wrong here.
#endif

Избегите бросать указатели на не указатели. Необходимо обычно избегать бросать указатель на неуказательный тип по любой причине (особенно при выполнении адресной арифметики). Альтернативы описаны в Предотвращении Преобразования Указателя на целое число.

Обновите ассемблерный код. Любой ассемблерный код должен быть переписан, потому что 64-разрядный ассемблер Intel существенно отличается от своего 32-разрядного дубликата. Для получения дополнительной информации посмотрите Код Ассемблера Портирования.

Любой ассемблерный код, непосредственно имеющий дело со структурой штабеля (в противоположность простому использованию указателей на переменные на штабеле) должен быть изменен для работы в 64-разрядной среде. Для получения дополнительной информации посмотрите OS X ABI Мужественная Ссылка Формата файла.

Фиксируйте строки формата. Печать функционирует такой как printf может быть хитрым при записи кода для поддержки 32-разрядных и 64-разрядных платформ из-за изменения в размерах указателей. Решить эту проблему для целых чисел размера указателя (uintptr_t) и другие стандартные типы, различные макросы существуют в inttypes.h заголовочный файл.

Строки формата для различных типов данных описаны в Таблице 3-1. Эти дополнительные типы, перечисленные в inttypes.h заголовочный файл, описаны в Таблице 3-2.

Табличные 3-1  строки Стандартного формата

Ввести

Строка формата

int

%d

long

%ld

long long

%lld

size_t

%zu

ptrdiff_t

%td

любой указатель

%p

  Дополнительная таблица 3-2 inttypes.h строки формата (где N является некоторым числом),

Ввести

Строка формата

intN_t (такой как int32_t)

PRIdN

uintN_t

PRIuN

int_leastN_t

PRIdLEASTN

uint_leastN_t

PRIuLEASTN

int_fastN_t

PRIdFASTN

uint_fastN_t

PRIuFASTN

intptr_t

PRIdPTR

uintptr_t

PRIuPTR

intmax_t

PRIdMAX

uintmax_t

PRIuMAX

Например, для печати intptr_t переменная (целое число размера указателя) и указатель, Вы пишете код, подобный этому в Перечислении 3-2.

Перечисление 3-2  Архитектурно-независимая печать

#include <inttypes.h>
void *foo;
intptr_t k = (intptr_t) foo;
void *ptr = &k;
 
printf("The value of k is %" PRIdPTR "\n", k);
printf("The value of ptr is %p\n", ptr);

Тип данных и подсказки по выравниванию

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

Будьте осторожны при смешивании целых чисел и длинных целых. Размер и выравнивание long целые числа и указатели изменились от 32-разрядного до 64-разрядного.

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

Если Вы часто перемещаете данные между переменными типа int и long, изменение в размере long может вызвать проблемы. Вы будете видеть различные связанные проблемы всюду по этому разделу.

Обязательно управляйте выравниванием совместно используемых данных. Выравнивание long long (64-разрядные) целые числа изменились от 32-разрядного до 64-разрядного. Это изменение выравнивания может изложить проблему при обмене данными между 32-разрядным и 64-разрядным кодом.

В Перечислении 3-3 изменяется выравнивание даже при том, что типы данных являются тем же размером.

  Выравнивание перечисления 3-3 long long целые числа в структурах

struct bar {
    int foo0;
    int foo1;
    int foo2;
    long long bar;
};

Когда этот код компилируется с 32-разрядным компилятором, переменной bar начинает 12 байтов с запуска структуры. Когда тот же код компилируется с 64-разрядным компилятором, переменной bar начинает 16 байтов с запуска структуры, и 4-байтовая клавиатура добавляется после foo2.

Если необходимо поддержать совместимость структуры данных, чтобы позволить единственной структуре данных быть совместно использованной, можно использовать прагму для принуждения упакованного режима выравнивания для каждой структуры по мере необходимости. Тогда добавьте надлежащие байты клавиатуры (если необходимый) для получения желаемого выравнивания. Пример показан в Перечислении 3-4.

Если назад совместимость с существующими структурами не важна, необходимо переупорядочить структуру данных так, чтобы самые большие поля были в начале структуры. Тем путем 8-байтовые поля начинаются при смещении 0 и таким образом выровненные на 8-байтовых границах без потребности добавить прагму выравнивания.

Перечисление 3-4  Используя прагмы для управления выравниванием

#pragma pack(4)
struct bar {
    int foo0;
    int foo1;
    int foo2;
    long long bar;
};
#pragma options align=reset

Необходимо использовать эту опцию только при необходимости, потому что существует потеря производительности для неправильно выровненных доступов.

Используйте sizeof с malloc. Так как указатели и длинные целые больше не 4 байта длиной, никогда не вызывайте malloc с явным размером (например, malloc(4)) выделять площадь для них. Всегда используйте sizeof получить корректный размер.

Никогда не предполагайте знание размера любой структуры (содержащий указатель или иначе); всегда используйте sizeof узнать наверняка. Для предотвращения будущих проблем мобильности ищите код любой экземпляр malloc это не сопровождается sizeof. grep команда и регулярные выражения являются Вашим другом, хотя использование Находит в меню Xcode Edit, может выполнить работу.

64-разрядный sizeof возвращает size_t. Обратите внимание на то, что sizeof возвращает целое число типа size_t. Поскольку размер size_t изменился на 64 бита, не передавайте значение функции в параметре размера int (если Вы не уверены, что размер не может быть настолько большим). Если Вы сделаете, то усечение произойдет.

Используйте явный (фиксированная ширина) типы C99. Необходимо использовать явные типы, если это возможно. Например, типы с именами как int32_t и uint32_t всегда будет 32-разрядное количество, независимо от будущих изменений в архитектуре.

32-разрядный тип

Предложенный тип C99

char или unsigned char (только, когда используется в качестве однобайтового целого числа)

int8_t или uint8_t

short или unsigned short

int16_t или uint16_t

int или unsigned int

int32_t или uint32_t

long или unsigned long

int32_t или uint32_t

long long или unsigned long long

int64_t или uint64_t

Наблюдайте за ошибками преобразования. Преобразование более коротких типов к 64-разрядному longs может привести к неожиданным результатам в определенных случаях. Обязательно считайте Правила Расширения знака для C и Языков C-derived, если Вы видите неожиданные значения от смешивающейся математики int и long переменные.

Используйте 64-разрядные типы для результатов адресной арифметики с указателями. Поскольку размер указателей является 64-разрядным значением, результатом адресной арифметики с указателями является также 64-разрядное значение. Необходимо всегда хранить эти значения в переменной типа ptrdiff_t гарантировать, что переменная измерена соответственно.

Избегите усекать позиции файла и смещения. Несмотря на то, что операции файла всегда использовали 64-разрядные позиции и смещения, необходимо все еще проверить на ошибки в их использовании. Ошибки станут более важными, когда растут размеры общего файла. Использовать fpos_t для позиции файла и off_t для файлового смещения.

Будьте осторожны со списками аргумента переменной. Списки аргумента переменной (varargs) не предоставляйте информацию типа для параметров, и параметры не продвинуты на большие типы автоматически. Если необходимо различить различные входные типы данных, Вы, как ожидают, будете использовать строку формата или другой подобный механизм для предоставления той информации varargs функция. Если функция вызова правильно не предоставляет ту информацию (или если varargs функция не интерпретирует его правильно), Вы получите неправильные результаты.

В частности если Ваш varargs функция ожидает a long введите и Вы передаете в 32-разрядном значении, varargs функция будет содержать 32 бита данных и 32 бита мусора от следующего параметра (который Вы потеряете в результате). Аналогично, если Ваш varargs функция ожидает int введите и Вы передаете в a long, Вы получите только половину данных, и остальные неправильно появятся в следующем параметре.

Например, если Вы используете неправильный printf строки формата, Вы получите неправильное поведение. Некоторые примеры этих ошибок строки формата показаны в общих Подсказках по Программированию.

Предотвращение преобразования указателя на целое число

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

int *c = something passed in as an argument....
int *d = (int *)((int)c + 4); // This code is WRONG!

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

int *c = something passed in as an argument....
int *d = c + 1;

(Конечно, этот пример несколько изобретен, и такое использование указателей является относительно редким.)

Больше типичной проблемы хранит указатель временно в переменной типа int. В большинстве случаев компилятор предупредит Вас, что указатель присваивается целому числу различного размера. Однако в нескольких случаях, код, содержащий такое присвоение, скомпилирует без предупреждения. Например, если код хранит значения в переменной типа long и затем более поздние копии это к целому числу, сам указатель не является непосредственно усеченным, таким образом, компилятор может не генерировать предупреждение. Эти проблемы особенно трудно определить.

Наконец, типичной проблемой является потребность сместить указатель определенным числом байтов. Вместо того, чтобы бросить к целому числу и использовать целочисленную математику, необходимо бросить указатель на тип указателя ширины байта такой как char * или uint8_t *. После того, как Вы сделаете это, указатель будет вести себя как целое число в арифметических целях. Например:

int *myptr = getPointerFromSomewhere();
int *shiftbytwobytes = (int *)(((int)myptr) + 2);

может быть переписан как:

int *myptr = getPointerFromSomewhere();
int *shiftbytwobytes = (int *)(((char *)myptr) + 2);

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

Работа с битами и битовыми масками

При работе с битами и масках с 64-разрядными значениями, необходимо стараться избежать получать 32-разрядные значения непреднамеренно. Вот некоторые подсказки для помощи Вам:

Сместитесь тщательно. Если Вы смещаетесь через биты, сохраненные в переменной типа long, не предполагайте, что переменная имеет определенную длину. Вместо этого используйте значение LONG_BIT определить число битов в a long. Результат сдвига, превышающего длину переменной, является архитектурно-зависимым.

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

Перечисление 3-5  Используя инвертированную маску для расширения знака

function_name(long value)
{
    long mask = ~0x3; // 0xfffffffc or 0xfffffffffffffffc
    return (value & mask);
}

В коде обратите внимание на то, что верхние биты в маске заполнены в 64-разрядном случае.

Подсказки по инструментам

Вот некоторые подсказки, чтобы помочь Вам использовать компилятор эффективнее в переходе Вашего кода к 64-разрядному:

Можно найти подробные подсказки и информацию о 64-разрядных изменениях инструментов в Компиляции 64-разрядного Кода.

Прагмы выравнивания

Иногда, разработчики используют прагмы выравнивания для изменения способа, которым структуры данных размечаются в памяти. Они обычно делают это для обратной совместимости. Во многих случаях Apple добавил прагмы для поддержания совместимости структуры данных между основанным на 68K и основанным на PowerPC кодом, работающим на той же машине под Mac OS 9 и ранее. OS X сохранил эти переопределения выравнивания для поддержания совместимости на уровне двоичных кодов с существующими структурами данных Углерода между Mac OS 9 и OS X.

Существует стоимость производительности, связанная с прагмами, однако; доступы памяти к невыровненным полям данных приводят к потере производительности. Поскольку нет никакого существующего 64-разрядного OS X приложений GUI, с которыми быть совместим, не необходимо сохранить совместимость на уровне двоичных кодов для этих структур данных в 64-разрядных приложениях. Таким образом, для улучшения общей производительности, при компиляции 64-разрядных исполнимых программ, версия OS X GCC игнорирует запросы на mac68k выравнивание.

При использовании этой прагмы только для доступа к структурам данных Apple, Вы не должны должны быть вносить любые изменения кода в свой код. При компиляции 64-разрядного кода компилятор игнорирует прагмы, и код работает правильно. Если, однако, Вы в настоящее время используете mac68k прагма выравнивания в Ваших собственных структурах данных, которые будут совместно использованы 32-разрядными и 64-разрядными версиями Вашего приложения (или если Вы используете mac68k прагма для структуры данных, соответствующей расположению регистра физического устройства), необходимо переписать структуру данных, чтобы использовать упакованное выравнивание и дополнить структуру соответственно.

За исключением типов данных Altivec, следующий код эквивалентен mac68k выравнивание:

#pragma pack(2)
...structure declaration goes here...
#pragma options align=reset

Точно так же за исключением некоторых векторных типов данных, следующий код эквивалентен стандартному 32-разрядному выравниванию:

#pragma pack(4)
...structure declaration goes here...
#pragma options align=reset

Правила расширения знака для C и языков C-derived

C и подобные языки используют ряд правил расширения знака определить, обработать ли главный бит в целом числе как знаковый бит, когда значение присваивается переменной большей ширины. Правила расширения знака следующие:

  1. Сумма значения со знаком и значения без знака того же размера является значением без знака.

  2. Любое продвижение всегда приводит к типу со знаком, если тип со знаком не может содержать все значения исходного типа (т.е. если получающийся тип не является тем же размером как исходный тип).

  3. Значения без знака являются расширенным нулем (не, подписываются расширенный), когда продвинуто на больший тип.

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

  5. Константы (если не изменено суффиксом, такой как 0x8L) обрабатываются как самый маленький размер, который будет содержать значение. Числа, записанные в шестнадцатеричном, могут быть обработаны компилятором как signed и unsigned int, long, и long long типы. Десятичные числа будут всегда обрабатываться как signed типы.

Перечисление 3-6 показывает пример неожиданного поведения, следующего из этих правил вместе с сопроводительным объяснением.

  Пример Расширения знака перечисления 3-6 1

int a=-2;
unsigned int b=1;
long c = a + b;
long long d=c; // to get a consistent size for printing.
 
printf("%lld\n", d);

Проблема: Когда этот код выполняется на 32-разрядной архитектуре, результат -1 (0xffffffff). Когда код выполняется на 64-разрядной архитектуре, результат 4294967295 (0x00000000ffffffff), который является, вероятно, не, что Вы ожидали.

Причина: Почему это происходит? Во-первых, эти два числа добавляются. Значение со знаком плюс значение без знака приводит к значению без знака (правило 1). Затем, то значение продвинуто на больший тип. Это продвижение не вызывает расширение знака (правило 2).

Решение: Для решения этой проблемы совместимым способом на 32 бита бросить b к long. Этот бросок вызывает не, подписывают расширенное продвижение b к 64-разрядному типу до дополнения, таким образом вынуждая целое число со знаком быть продвинутым (способом со знаком) для соответствия. С тем изменением результатом является ожидаемый -1.

Перечисление 3-7 показывает связанный пример с сопроводительным объяснением.

  Пример Расширения знака перечисления 3-7 2

unsigned short a=1;
unsigned long b = (a << 31);
unsigned long long c=b;
 
printf("%llx\n", c);

Проблема: ожидаемый результат (и результат 32-разрядной исполнимой программы) 0x80000000. Результат, сгенерированный 64-разрядной исполнимой программой, однако, 0xffffffff80000000.

Причина: Почему расширяется этот знак? Во-первых, когда оператор сдвига вызывается, переменная a продвинут на переменную типа int. Поскольку все значения a short может вписаться в со знаком int, результат этого продвижения подписывается (правило 3).

Второй, когда сдвиг завершился, результат был сохранен в a long. Таким образом, 32-разрядное значение со знаком, представленное (a << 31) был знак, расширенный (правило 4), когда это было продвинуто на 64-разрядное значение (даже при том, что получающийся тип без знака).

Решение: Для решения этой проблемы необходимо бросить начальное значение к a long до сдвига. Таким образом короткое будет продвинуто только один раз — на сей раз к 64-разрядному типу (по крайней мере, когда скомпилировано как 64-разрядная исполнимая программа).

Скоростной механизм и подсказки по выравниванию SSE

Несмотря на то, что SSE и Скоростной Механизм C и интерфейсы ассемблера не изменились для 64-разрядного при использовании этих технологий необходимо рассмотреть любой код, пытающийся выровнять указатели на 16-байтовые адреса для обработки.

Например, следующий код содержит две ошибки:

TYPE *aligned = (TYPE *) ((int) misalignedPtr & 0xFFFFFFF0); // BAD!

Во-первых, указатель брошен к int значение, приводящее к усечению. Даже после того, как эта проблема решена, однако, указатель все еще будет усеченным потому что постоянное значение 0xFFFFFFF0 не 64-разрядное значение.

Вместо этого этот код должен быть записан как:

#include <stdint.h>
TYPE *aligned = (TYPE *) ((intptr_t) misalignedPtr & ~(intptr_t)0xF);

Портирование кода ассемблера

В этом разделе описываются некоторые проблемы, вовлеченные в портирование кода ассемблера к 64-разрядному приложению. На архитектуре Intel, в дополнение к проблемам, описанным в этом разделе, необходимо значительно изменить любой код ассемблера, имеющий дело со штабелем непосредственно, потому что 64-разрядный ABI отличается значительно от 32-разрядного ABI. Предмет стековых фреймов выходит за рамки этого раздела. Для получения дополнительной информации посмотрите OS X ABI Мужественная Ссылка Формата файла.

На основанных на Intel компьютерах Macintosh 64-разрядный код использует Intel 64 (раньше EM64T) расширения ассемблера Intel ISA. Этот раздел суммирует различия между кодом Intel 64 и кодом IA32 с точки зрения их влияния на регистры и системы команд.

Изменения регистра

64-разрядные регистры на Intel имеют различные имена, чем их 32-разрядные дубликаты. Кроме того, существует больше из них. Эти имена регистра перечислены в Таблице 3-3.

Табличное 3-3  именование Регистра на 32-разрядных и 64-разрядных архитектурах Intel

IA32 32-разрядный регистр

Архитектура Intel 64 64-разрядный вариант

Описание

EIP

RIP

Указатель команд

EAX

RAX

Регистр общего назначения A

EBX

RBX

Регистр общего назначения B

ECX

RCX

Регистр общего назначения C

EDX

RDX

Регистр общего назначения D

ESP

RSP

Указатель вершины стека

EBP

RBP

Указатель кадра

ESI

RSI

Исходный индексный регистр

EDI

RDI

Целевой индексный регистр

----

R8 *

Зарегистрируйтесь 8 (новый)

----

R9 *

Зарегистрируйтесь 9 (новый)

----

R10 *

Зарегистрируйтесь 10 (новый)

----

R11 *

Зарегистрируйтесь 11 (новый)

----

R12 *

Зарегистрируйтесь 12 (новый)

----

R13 *

Зарегистрируйтесь 13 (новый)

----

R14 *

Зарегистрируйтесь 14 (новый)

----

R15 *

Зарегистрируйтесь 15 (новый)

Все новые регистры (R8 через R15) добавленный в системе команд архитектуры Intel 64 может также быть получен доступ как 32-разрядные, 16-разрядные, и 8-разрядные регистры. Например, регистр R8 может адресоваться следующими способами:

Имя регистра

Описание

R8

64-разрядный регистр.

R8d

32-разрядный регистр, содержащий нижнюю половину R8.

R8w

16-разрядный регистр, содержащий нижнюю половину R8d.

R8l (Нижний регистр «l»)

8-разрядный регистр, содержащий нижнюю половину R8w.

В дополнение к добавлению регистров общего назначения система команд Архитектуры Intel 64 имеет восемь дополнительных векторных регистров. В системе команд IA32 пронумерованы векторные регистры XMM0 через XMM7. Система команд Архитектуры Intel 64 расширяет это путем добавления XMM8 через XMM15.

Изменения инструкции

Большинство инструкций IA32 может взять 64-разрядные параметры. Все расширения системы команд IA32 через SSE3 включены как часть Архитектуры Intel 64. Кроме того, много новых инструкций были добавлены.

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

Для получения дополнительной информации

Для получения дополнительной информации о портировании и оптимизации ассемблера Intel кодируют для 64-разрядного, необходимо также читать: