Почему я получаю ошибку "cast from pointer to integer of different size"?

Следующая строка (чистая c) компилируется чисто на windows (win7 64 бит + кодовые блоки 13 + mingw32) и debian (wheezy 32 bits + codeblocks 10 + gcc), но вызывает предупреждение о kali (64 бита + кодовые блоки + gcc). Любые комментарии? Я имею в виду, почему я получаю это предупреждение, хотя одна и та же строка компилируется без предупреждения на windows и debian?

void* foo(void *dst, ...) {
    // some code
    unsigned int blkLen = sizeof(int); // this line ok.
    unsigned int offset = (unsigned int) dst % blkLen; // warning here!
    // some code cont...
}

Сообщение в кодовых блоках: " ошибка:, отличная от указателя до целого разного размера [-Werror = pointer-to-int-cast]"

note: мои параметры компилятора -std=c99 -Werror -save-temps (одинаковые для всех трех систем).

изменить 2: Хотя мне удалось скомпилировать его без предупреждения, используя строки препроцессора ниже, @Keith Thompson (см. Ниже) имеет решающее значение в этой проблеме. Итак, последнее решение - использовать uintptr_t - лучший выбор.

изменить 1: Спасибо, что все ответили. Как отмечают все ответы, проблема состоит в 32-разрядной версии 64-разрядной версии. Я вставил следующие строки препроцессора:

#if __linux__   //  or #if __GNUC__
    #if __x86_64__ || __ppc64__
        #define ENVIRONMENT64
    #else
        #define ENVIRONMENT32
    #endif
#else
    #if _WIN32
        #define ENVIRONMENT32
    #else
        #define ENVIRONMENT64
    #endif
#endif // __linux__

#ifdef ENVIRONMENT64
    #define MAX_BLOCK_SIZE unsigned long long int
#else
    #define MAX_BLOCK_SIZE unsigned long int
#endif // ENVIRONMENT64

а затем заменил проблемную строку следующим образом:

unsigned int offset = (MAX_BLOCK_SIZE) dst % blkLen;

Теперь все кажется ОК.

Ответ 1

Причиной предупреждения является то, что компилятор подозревает, что вы, возможно, пытаетесь обогнуть указатель через int и обратно. Это было обычной практикой до появления 64-битных машин, и это небезопасно или разумно. Конечно, здесь компилятор может ясно видеть, что вы этого не делаете, и было бы неплохо, если бы оно было достаточно умным, чтобы избежать предупреждения в таких случаях, но это не так.

Чистая альтернатива, которая позволяет избежать предупреждения, а другая гораздо более неприятная проблема с неправильным результатом, когда преобразованное значение отрицательное, следующее:

unsigned int offset = (uintptr_t) dst % blkLen;

Вам нужно включить stdint.h или inttypes.h, чтобы иметь uintptr_t.

Ответ 2

Проблема заключается в том, что преобразование указателя void* в unsigned int по своей сути не переносимо.

Возможная разница в размере - это только часть проблемы. Эту часть проблемы можно решить, используя uintptr_t, тип, определенный в <stdint.h> и <inttypes.h>. uintptr_t гарантированно будет достаточно широким, чтобы преобразование a void* в uintptr_t и обратно снова даст исходное значение указателя (или, по крайней мере, значение указателя, которое сравнивается с исходным). Также существует тип intptr_t, который подписан; обычно беззнаковые типы имеют больше смысла для такого рода вещей. uintptr_t и intptr_t не гарантируются, но они должны существовать в любой (C99 или более поздней) реализации, которая имеет соответствующие целые типы.

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

В стандарте C в ненормативной сноске говорится, что:

Функции отображения для преобразования указателя на целое число или целое число с указателем предназначены для согласования с адресацией структура среды выполнения.

что не помогает, если вы не знаете, что такое структура адресации.

Кажется, вы пытаетесь определить, какое смещение аргумента void* относится к следующему более низкому краю blkLen; другими словами, вы пытаетесь определить, как значение указателя выравнивается по отношению к blkLen -размерным блокам памяти.

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

Конкретный пример: я работал над системами (векторные машины Cray), где указатель void* - это 64-разрядный машинный адрес (который указывает на 64-разрядное слово) с вставленным смещением 3-битного байта по программному обеспечению в неиспользуемые 3 разряда высокого порядка. Преобразование указателя в целое число просто копирует представление. Любая целочисленная арифметика для такого целого числа может привести к бессмысленным результатам, если только это не учитывает это (по общему признанию, экзотическое) представление.

Выводы:

  • Вы должны использовать uintptr_t, а не использовать трюки препроцессора, чтобы определить, какой тип целого вы можете использовать. Разработчик вашего компилятора уже выполнил работу по определению целочисленного типа, который может безопасно хранить преобразованное значение указателя. Нет необходимости изобретать это колесо. (Caveat: <stdint.h> был добавлен к стандарту ISO ISO 1999. Если вы застряли с использованием древнего компилятора, который его не реализует, вам все равно придется использовать какие-то хаки #ifdef. по-прежнему предлагайте использовать uintptr_t, если он доступен. Вы можете протестировать __STDC_VERSION__ >= 199901L для проверки соответствия C99, хотя некоторые компиляторы могут поддерживать <stdint.h> без полной поддержки C99.)

  • Вам нужно знать, что преобразование указателя в целое число и воспроизведение его значения не переносимы. Это не значит, что вы не должны этого делать; одной из самых сильных сторон C является его способность поддерживать не переносимый код, когда это вам нужно.

Ответ 3

Поскольку приведение void * в unsigned int - это именно то, что это предупреждение предназначено для улова, потому что оно небезопасно. Указатель может быть 64-битным, а int может быть 32-битным. Для любой данной платформы sizeof(unsigned int) не гарантируется sizeof(void *). Вместо этого вы должны использовать uintptr_t.

Ответ 4

Может быть, потому что в 64-битной архитектуре указатель имеет длину 64 бит и int всего 32 бита?

Вы должны попробовать

void* foo(void *dst, ...) {
    // some code
    unsigned int blkLen = sizeof(int); // this line ok.
    uintptr_t offset = (uintptr_t) dst % blkLen; // warning here!
    // some code cont...
}

Ответ 5

Я думаю, вы получите предупреждение, потому что размер int зависит от реализации, например, int может составлять 2 байта или 4 байта. Это может быть причиной предупреждения (Пожалуйста, поправьте меня, если я ошибаюсь). Но почему вы пытаетесь сделать по модулю указатель.

Ответ 6

У вас есть макрос, но вы не думаете, что он по-прежнему не прав. Поскольку ваш указатель будет преобразован в unsigned long long int или unsigned long int, который будет 32-битным и 64-битным в 86x и 64x OS, но смещение переменной равно unsigned int, которое равно 32 бит в 64x и 86x OS. Поэтому я думаю, что вы должны преобразовать смещение также в соответствующий макрос.

Или просто вы можете преобразовать указатель в длинный (т.е. unsigned int long) и смещать в long (т.е. unsigned int to long).