Почему я не могу static_cast между char * и unsigned char *?

По-видимому, компилятор считает их несвязанными типами и, следовательно, требуется reinterpret_cast. Почему это правило?


person Nick    schedule 14.04.2012    source источник
comment
Я беру хэш SHA-1 строки. c_str() возвращает const char *, а функция SHA-1 принимает const unsigned char * в качестве аргумента.   -  person Nick    schedule 14.04.2012
comment
И что вы ожидаете, если эта строка содержит отрицательные значения символов?   -  person Pubby    schedule 14.04.2012
comment
Я ожидаю, что любое отрицательное значение c станет c + 256, как это принято при преобразовании байта со знаком в байт без знака. Честно говоря, я просто делаю преобразование, чтобы вычислить хеш-значение. Меня не волнует, как они конвертируются, пока они конвертируются одинаково каждый раз.   -  person Nick    schedule 14.04.2012
comment
@Nick: Преобразование char в unsigned char — это преобразование. Преобразование char * в unsigned char* и последующее чтение элементов, предполагая, что они были преобразованы, когда это не так, сильно отличается. Он будет работать в системе, где преобразование на самом деле не требует изменения представления (например, в системе дополнения до двух), но, поскольку это предположение, зависящее от реализации, уместно, что требуется явное reinterpret_cast.   -  person CB Bailey    schedule 14.04.2012


Ответы (3)


Они совершенно разных типов см. стандарт:

3.9.1 Основные типы [basic.fundamental]

1 Объекты, объявленные как символы char), должны быть достаточно большими для хранения любого члена базового набора символов реализации. Если символ из этого набора хранится в символьном объекте, интегральное значение этого символьного объекта равно значению односимвольной литеральной формы этого символа. Реализация определяет, может ли объект char содержать отрицательные значения. Символы могут быть явно объявлены беззнаковыми или
подписанными. Обычный символ, подписанный символ и неподписанный символ — это три разных типа. Символ, знаковый символ и неподписанный символ занимают одинаковый объем памяти и имеют одинаковое выравнивание. требования (basic.types); то есть они имеют одно и то же объектное представление. Для символьных типов все биты представления object
участвуют в представлении значения. Для типов символов без знака все возможные битовые комбинации представления значения представляют числа. Эти требования не распространяются на другие типы. В любой конкретной реализации простой объект char может принимать те же значения, что и подписанный char, или неподписанный char; какой из них определяется реализацией.

По аналогии с этим также происходит следующее:

unsigned int* a = new unsigned int(10);
int* b = static_cast<int*>(a); // error different types

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

unsigned int a = new unsigned int(10);
int b = static_cast<int>(a); // OK but may result in loss of precision

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

Бьерн Страутроп объясняет, почему static_cast полезны в этой ссылке: http://www.stroustrup.com/bs_faq2.html#static-cast, но в сокращенной форме пользователь должен четко указать свои намерения и дать компилятору возможность проверить, может ли быть достигнуто то, что вы намереваетесь, поскольку static_cast не поддерживает приведение между различными типами указателей, тогда компилятор может поймать эту ошибку, чтобы предупредить пользователя, и если они действительно хотят выполнить это преобразование, им следует использовать reinterpret_cast.

person EdChum    schedule 14.04.2012
comment
Спасибо за указание стандарта здесь. У меня нет в наличии. - person Tobias Langner; 14.04.2012
comment
Итак, если это разные типы, почему компилятор разрешает приведение unsigned char a = 255; char b = static_cast<char>(a);? - person Nick; 14.04.2012
comment
по той же причине вы можете static_cast из doubles в int и наоборот, чего вы не можете сделать, так это static_cast double* в int*, типы указателей разные, но вы можете конвертировать из одного значения в другое с оговоркой, что могут быть потеря точности - person EdChum; 14.04.2012
comment
хм, похоже на глупое правило. Как я упоминал в своем ответе Тобиасу, кажется, что единственный раз, когда static_cast должен быть разрешен для типов массива/указателя, должен быть для примитивов одинаковой ширины, иначе вы могли бы случайно выстрелить себе в ногу ( как в примере, который я привел). По крайней мере, с примитивами одинаковой ширины ничего не может пойти не так. - person Nick; 14.04.2012
comment
@Nick Людей может удивить, когда они узнают, что вы не можете static_cast между unsigned char* и char*, но в основном это потому, что это разные типы, мы не удивлены тем, что вы можете статически приводить между связанными типами, такими как floats в ints, а на самом деле беззнаковый char в char, но static_cast делает то, что делает. Явное преимущество перед приведениями в стиле c заключается в том, что вы получаете ошибки времени компиляции, если пытаетесь преобразовать между разными типами, как в вашем случае, есть соответствующий пост SO: stackoverflow.com/questions/2473628/ - person EdChum; 14.04.2012
comment
в этом случае это явно неправильно, поскольку int и double имеют разную ширину (и даже не представлены одинаково!), поэтому, если вы приведете к double *, вы можете случайно затоптать часть памяти, если сделаете *d = 3.14. Мое использование глупости относится только к примитивам одинаковой ширины. - person Nick; 14.04.2012
comment
@Ник, это может быть глупо для тебя, но в качестве другого примера static_cast невозможно между unsigned int* и int*, что аналогично твоему вопросу, хранилище и ширина одинаковы для обоих, но static_cast обнаруживает во время компиляции, что они оба разных типов - person EdChum; 14.04.2012
comment
@Ник: Даже с int и float. Они могут иметь одинаковый размер, но если у вас есть int, то попробуйте прочитать его как float, тогда то, что вы получите, зависит от того, как именно float хранится в памяти. А так как спецификация не говорит, как float хранится в памяти, спецификация не может определить, как выглядит int. Помните: стандарт C++ существует, чтобы гарантировать то, что вы получаете. Чтобы определить это поведение, стандарт должен подробно описать, как float размещается в памяти, а также как int размещается в памяти. - person Nicol Bolas; 15.04.2012
comment
@Nick: Короче говоря: вы путаете концепцию того, что происходит на реальных машинах, с тем, что требует спецификация. - person Nicol Bolas; 15.04.2012
comment
В спецификации не говорится, что старший бит должен быть битом знака, поэтому в реализации он может быть младшим битом. Если бы это было так, когда компилятор преобразовывал бы беззнаковый char со значением 1 в знаковый char, он знал бы, что ему нужно сдвинуть биты влево на 1, чтобы учесть бит знака. Но с беззнаковыми символами char* и char* корректировка значений в месте нахождения указателя не является задачей приведения. В любом случае он не будет знать, сколько символов нужно настроить. - person Cemafor; 13.03.2015
comment
Часть стандарта, указанная в этом ответе, напоминает мне о генераторах справочных страниц git. - person allyourcode; 02.12.2015
comment
static_cast, однако, является сукой, допускающей безусловное повышение в дереве вывода. - person Euri Pinhollow; 09.07.2018

вы пытаетесь преобразовать несвязанные указатели с помощью static_cast. static_cast не для этого. Здесь вы можете увидеть: Приведение типов.

С помощью static_cast вы можете преобразовывать числовые данные (например, должен работать char в unsigned char) или указатели на связанные классы (связанные некоторым наследованием). Это не так. Вы хотите преобразовать один несвязанный указатель в другой, поэтому вам нужно использовать reinterpret_cast.

По сути, то, что вы пытаетесь сделать, для компилятора то же самое, что и попытка преобразовать char * в void *.


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

char x = 5;
unsigned char y = static_cast<unsigned char>(x);

что еще можно:

double d = 1.2;
int i = static_cast<int>(d);

Если вы посмотрите на этот код на ассемблере, вы увидите, что второе приведение — это не просто повторная интерпретация битового шаблона d, а вместо этого здесь вставлены некоторые ассемблерные инструкции для преобразований.

Теперь, если мы распространим это поведение на массивы, случай, когда достаточно просто другого способа интерпретации битового шаблона, это может сработать. Но как насчет приведения массивов двойных значений к массивам целых чисел? Здесь вы либо должны заявить, что вам просто нужна переинтерпретация битовых шаблонов - для этого есть механизм, называемый reinterpret_cast, либо вы должны выполнить дополнительную работу. Как видите, простого расширения static_cast для указателей/массивов недостаточно, поскольку он должен вести себя аналогично static_casting одиночных значений типов. Иногда для этого требуется дополнительный код, и неясно, как это должно быть сделано для массивов. В вашем случае - остановка на \0 - потому что это соглашение? Этого недостаточно для нестроковых случаев (число). Что произойдет, если размер типа данных изменится (например, int или double на x86-32bit)?

Поведение, которое вы хотите, не может быть правильно определено для всех вариантов использования, поэтому оно не входит в стандарт C++. В противном случае вам пришлось бы помнить такие вещи, как: «Я могу привести этот тип к другому, если они имеют целочисленный тип, имеют одинаковую ширину и ...». Таким образом, совершенно ясно - либо они связаны КЛАССЫ - тогда вы можете приводить указатели, либо они являются числовыми типами - тогда вы можете приводить значения.

person Tobias Langner    schedule 14.04.2012
comment
В своем вступительном сообщении я признал, что компилятор говорит, что это несвязанные указатели. Я хочу знать почему. Мне кажется, что если T1 связано с T2, то T1 * должно быть связано с T2 *. Почему это правило набора текста не работает (для примитивных типов)? - person Nick; 14.04.2012
comment
@Ник, это не как-то связанные, а связанные классы. Как вы сказали, char и unsigned char являются примитивами, а не классами. Вот причина - и это то, что я сказал, если вы внимательно прочитали. Вы правы - если класс T1 связан с классом T2, то вы можете использовать static_cast для преобразования T1* в T2*. Это не то, что вы делаете. char не связан с unsigned char в смысле отношения, требуемого стандартом C++. - person Tobias Langner; 14.04.2012
comment
Однако, если вы отбросите указатель, у компилятора не возникнет проблем с преобразованием между любыми примитивными типами, например. unsigned char a = 255; char b = static_cast<char>(a); Это кажется немного странным, так как если T1 и T2 являются классами, приведение между указателями не звучит, так как вы могли бы сделать что-то вроде: class A; class B : public A; B *b = new B[4]; b[0] = B(); A *a = static_cast<A *>(b); a[1] = A(); B b1 = b[1]; // oops Кажется, единственный раз приведение должно быть безопасным между примитивными типами. - person Nick; 14.04.2012
comment
да - и поведение для этого ясно и четко определено. У вас есть некоторые правила преобразования одного числового типа в другой. Это может быть так же просто, как копирование битовой комбинации в новую память, или так же сложно, как преобразование типа double в тип int. Тем не менее - это только для одного значения и, следовательно, легко определяется. Смотрите мое объяснение выше. static_cast имеет 2 совершенно разных использования в зависимости от того, является ли тип типом указателя или нет. Они должны были называть его по-разному для двух вариантов использования (например, static_cast и numeric_cast). - person Tobias Langner; 14.04.2012

Помимо указателей, unsigned char * и char * не имеют ничего общего (EdChum уже упоминал тот факт, что char, signed char и unsigned char — это три разных типа). Вы можете сказать то же самое для типов указателей Foo * и Bar * на любые непохожие структуры.

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

person Michael Foukarakis    schedule 14.04.2012