Смысл типа в языке, подобном C, состоит в том, что он описывает некоторый четко определенный, полезный набор значений.
Значение типа unsigned int
может содержать любоецелое число в диапазоне, определенном вашим компилятором и процессором.Обычно это 32-разрядное целое число, что означает, что unsigned int
может содержать любое целое число от 0 до 4294967295. Но unsigned int
не может содержать значение 5000000000 (оно слишком большое) или значение 123.456 (это не целое число).) или значение "привет, мир" (строки не являются целыми числами).
Значение типа char *
может содержать указатель на символ в любом месте используемого адресного пространства на вашем компьютере.Таким образом, он может содержать указатель на один символ, или он может содержать указатель на массив символов с нулем в конце, например «hello, world», или он может содержать указатель NULL.Но оно не предназначено для хранения целого числа или значения с плавающей запятой.
Иногда, в стесненных или необычных обстоятельствах, программисты пытаются согнуть правила, вклинивая значение одного типа в переменнуюдругой тип.Иногда вы можете сделать эту работу, а иногда нет.Это почти всегда очень плохая идея.Даже если это можно заставить работать.часто бывает так, что он работает правильно на одной машине, но не на других.
Давайте посмотрим внимательнее на то, что вы делаете.(Я заполняю некоторые детали, которые вы пропустили.)
void expects_unsigned_int(unsigned int);
Здесь мы сообщаем компилятору, что будет функция с именем expects_unsigned_int
, которая принимает один аргумент типа unsigned int
и возвращаетничего.
#include <stdio.h>
int main()
{
expects_unsigned_int("some text");
}
Здесь мы вызываем эту функцию, передавая аргумент типа char *
.Конечно, у нас проблемы.Вы не можете втиснуть char *
в слот размером unsigned int
.Правильный компилятор даст вам серьезное предупреждение, если не прямую ошибку, здесь.Шахта говорит
warning: passing argument 1 of ‘expects_unsigned_int’ makes integer from pointer without a cast
expected ‘unsigned int’ but argument is of type ‘char *’
Эти предупреждения имеют смысл и согласуются с моими объяснениями того, что мы должны и не должны делать с типами.
Как вы, возможно, знаете, указатель«просто» адрес, и на большинстве машин адрес «всего лишь» представляет собой битовый образец некоторого размера, так что вы можете убедить себя, что должна быть возможность заклинить указатель в целое число.Ключевой вопрос, к которому мы вернемся через минуту, заключается в том, является ли тип unsigned int
буквально достаточно большим, чтобы вместить все возможные значения типа char *
.
void expects_unsigned_int(unsigned int val) {
Здесь мы начинаем определять деталифункции expects_unsigned_int
.Мы снова говорим, что он принимает один аргумент типа unsigned int
и ничего не возвращает.Это согласуется с более ранним объявлением прототипа.Хорошо, пока.
unsigned int* string = 0;
Здесь мы объявляем указатель типа unsigned int *
и инициализируем его нулевым указателем.Нам действительно не нужен этот промежуточный указатель, и в этом случае не имеет значения, инициализируем ли мы его, поскольку мы собираемся перезаписать его.
string = (unsigned int*) val;
Вот здесь и начинается проблема.У нас есть значение типа unsigned int, и мы пытаемся преобразовать его в указатель.Опять же, это может показаться разумным, поскольку указатели являются «просто» адресами, а адреса - «просто» битовыми шаблонами.
Другая вещь, которую мы имеем, это явное приведение.В этом случае, как ни странно, актерский состав на самом деле не «выполняет» преобразование из unsigned int
в unsigned int *
.Если бы мы написали присваивание без приведения, например:
string = val;
, компилятор увидит значение unsigned int
справа и указатель типа unsigned int *
слевасторона, и он будет пытаться выполнить то же преобразование неявно.Но поскольку это опасное и потенциально бессмысленное преобразование, компилятор предупредит об этом.Мой говорит:
warning: assignment makes pointer from integer without a cast
Но когда вы пишете явное приведение, для большинства компиляторов это означает: «Поверьте мне, я знаю, что я делаю, сделайте это преобразование и оставьте свои сомнения при себе, яне хочу слышать ни одного из ваших предупреждений. "
Наконец,
printf("%s", (char*)string);
Здесь мы делаем две вещи.Сначала мы явно преобразуем указатель unsigned int *
в указатель char *
.Это также сомнительное обращение, но гораздо менее серьезное беспокойство.На подавляющем большинстве современных компьютеров все указатели (независимо от того, на что они указывают) имеют одинаковый размер и представление, поэтому такое преобразование вряд ли вызовет какие-либо проблемы.
И затем второе, что мыНаконец, попробуйте напечатать указатель char *
, используя printf
и %s
.Как вы обнаружили, это не всегда работает.На моем компьютере он тоже не работает.
Есть компьютеры, на которых он будет работать, поэтому ответ на ваш вопрос "Возможно ли это сделать?"«Да, может быть, но».
Почему у вас это не сработало?Я не могу быть уверен, но, вероятно, по той же причине, что это не сработало для меня.На моей машине указатели 64-битные, а обычные целые (включая unsigned int) 32-битные.Поэтому, когда мы вызвали
expects_unsigned_int("some text");
и попытались вставить указатель в слот целого размера, мы удалили 32 из 64 бит.Это преобразование с потерей информации, поэтому, скорее всего, это будет неисправимая ошибка.
Давайте напишем некоторую дополнительную информацию, чтобы мы могли подтвердить, что это именно то, что происходит.Я рекомендую вам внести эти изменения в вашу программу на вашем компьютере, чтобы вы могли видеть, какие результаты вы получите.
Давайте перепишем main
так:
int main()
{
char *string = "some text";
printf("string = %p = %s\n", string, string);
printf("int: %d, pointer: %d\n", (int)sizeof(unsigned int), (int)sizeof(string));
expects_unsigned_int(string);
}
Мы используемprintf
формат %p
для печати указателя.Это покажет нам представление битового шаблона, который составляет значение указателя (каким бы большим оно ни было), обычно в шестнадцатеричном формате.Мы также используем sizeof()
, чтобы сообщить нам, какие большие целые числа и указатели на машине, которую мы используем.
Давайте перепишем expects_unsigned_int
следующим образом:
void expects_unsigned_int(unsigned int val) {
char *string = val;
printf("val = %x\n", val);
printf("string = %p\n", string);
printf("string = %s\n", string);
}
Здесь мыВы печатаете как значение val
по мере его поступления, так и указатель, который мы извлекаем из него (опять же, используя %p
).Кроме того, я делаю string
типа char *
, так как не было смысла иметь его unsigned int *
.
Когда я запускаю модифицированную программу, вот что я получаю:
string = 0x101295f20 = some text
int: 4, pointer: 8
val = 1295f20
string = 0x1295f20
Segmentation fault: 11
Сразу же мы видим несколько вещей:
- Указатели больше, чем целые на этой машине (как я говорил ранее).Мы никак не сможем вставить указатель в int без потери данных.
- Мы действительно удалили некоторые биты указателя sting.Он начинается с
101295f20
и заканчивается как 1295f20
. - Программа не работает.Сбой с нарушением сегментации, вероятно, из-за того, что значение искаженного указателя
0x1295f20
указывает за пределы его адресного пространства.
Так как же это исправить?Лучше всего было бы , а не пытаться передать значение указателя через слот, предназначенный для хранения целых чисел.
Или, если бы мы действительно этого хотели, если бы мы были связаны и решили преобразоватьуказатели на целые числа и обратно, мы могли бы попытаться использовать большее целое число, например unsigned long int
.(И если бы этого было недостаточно, мы могли бы попробовать unsigned long long int
.)
Я переписал main
так:
void expects_unsigned_long_int(unsigned long int val);
int main()
{
char *string = "some text";
printf("string = %p = %s\n", string, string);
printf("int: %d, pointer: %d\n", (int)sizeof(unsigned long int), (int)sizeof(string));
expects_unsigned_long_int(string);
}
А потом expects_unsigned_long_int
выглядит так:
void expects_unsigned_long_int(unsigned long int val) {
char *string = val;
printf("val = %x\n", val);
printf("string = %p\n", string);
printf("string = %s\n", string);
}
Я все еще получаю предупреждения при компиляции, но теперь, когда я запускаю его, он печатает
string = 0x10a09df20 = some text
int: 8, pointer: 8
val = a09df20
string = 0x10a09df20
string = some text
Так что, похоже, тип unsigned long int
достаточно большой (сейчас), и никакие биты не удаляются, и исходное значение указателя успешно восстанавливается внутри expects_unsigned_long_int
, и строка печатается правильно.
Но, в заключение, пожалуйста, найдите лучший способ сделать это!