Передача строки в функцию, когда ожидается неподписанное int в C - PullRequest
0 голосов
/ 27 мая 2018

В основном я вызываю эту функцию:

expects_unsigned_int("some text");

Определено так:

expects_unsigned_int(unsigned int val)

Я бы хотел напечатать строку, переданную внутри функции.Можно ли сделать это так, как определено expects_unsigned_int()?

Вот что я пробовал:

expects_unsigned_int(unsigned int val) {
    unsigned int* string = 0;
    string = (unsigned int*) val;
    printf("%s", (char*)string);
}

Но ничего не печатается.

Ответы [ 2 ]

0 голосов
/ 27 мая 2018

Смысл типа в языке, подобном 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, и строка печатается правильно.

Но, в заключение, пожалуйста, найдите лучший способ сделать это!

0 голосов
/ 27 мая 2018

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

char* pointer1 = "abcde";
unsigned int integer = pointer1;
char* pointer2 = integer;
if (pointer1 == pointer2) {
    printf("Works, kindof.\n");
}

Однако, как отмечали другие в комментариях, сам подход плох и вам не следует использоватьэто решить любую проблему, которая у вас есть.Вместо этого сначала прочитайте о значении «проблемы XY», а затем задайте другой вопрос, который решает актуальную проблему здесь.

...