当 C 中需要 unsigned int 时将字符串传递给函数

Passing string to a function when unsigned int expected in C

我主要调用这个函数:

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);
}

但是它不打印任何东西。

作为参数给出的字符串会衰减到其第一个元素的地址,然后将其转换为 unsigned int。如果该整数足够大,可以在不丢失位的情况下保存地址,您可以将其转换回来:

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

但是,正如其他人在评论中指出的那样,这种方法很糟糕,您不应该用它来解决您遇到的任何问题。相反,首先阅读 "XY problem" 的含义,然后提出另一个问题来解决此处的实际问题。

在像 C 这样的语言中,type 的全部意义在于它描述了一些定义明确、有用的值集。

unsigned int 类型的值可以包含编译器和处理器定义的范围内的任何整数。这通常是一个 32 位整数,这意味着 unsigned int 可以包含 0 到 4294967295 之间的任何整数。但是 unsigned int 不能包含值 5000000000(太大)或值 123.456(它不是整数)或值 "hello, world"(字符串不是整数)。

char * 类型的值可以包含指向您计算机上可用地址 space 中任意位置字符的指针。所以它可以保存一个指向单个字符的指针,或者它可以保存一个指向以 null 结尾的字符数组的指针,如 "hello, world",或者它可以保存一个 NULL 指针。但它不打算保存整数或浮点值。

有时,在受限或不寻常的情况下,程序员会试图通过将一种类型的值嵌入到不同类型的变量中来改变规则。有时你可以做到这一点,有时你不能。这几乎总是一个非常糟糕的主意。即使它可以工作。通常情况下,它在一台机器上可以正常工作,但在其他机器上不能正常工作。

让我们更仔细地看看你在做什么。 (我正在填写您遗漏的一些细节。)

void expects_unsigned_int(unsigned int);

这里我们告诉编译器将有一个名为 expects_unsigned_int 的函数,它接受一个 unsigned int 类型的参数并且 return 什么都没有。

#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 *’

这些警告是有道理的,并且与我到目前为止关于我们应该和不应该对类型做什么的解释是一致的。

如您所知,指针是 "just" 地址,在大多数机器上,地址是 "just" 某种大小的位模式,因此您可以说服自己它应该是可以将一个指针塞进一个整数。我们将在一分钟内 return 解决的关键问题是类型 unsigned int 是否足够大以容纳类型 char *.

的所有可能值
void expects_unsigned_int(unsigned int val) {

这里我们开始定义函数的细节expects_unsigned_int。我们再次说它接受一个 unsigned int 类型的参数,而 return 什么都没有。这与早期的原型声明是一致的。到目前为止还好。

unsigned int* string = 0;

这里声明了一个unsigned int *类型的指针,并初始化为空指针。我们并不真的需要这个中间指针,在这种情况下,我们是否初始化它并不重要,因为我们即将覆盖它。

string = (unsigned int*) val;

麻烦就从这里开始。我们有一个 unsigned int 值,我们试图将它转换成一个指针。同样,这似乎是合理的,因为指针是 "just" 地址,而地址是 "just" 位模式。

我们拥有的另一件事是显式转换。在这种情况下,令人惊讶的是,转换并不是真正的 "doing" 从 unsigned intunsigned int * 的转换。如果我们在没有强制转换的情况下编写作业,如下所示:

string = val;

编译器会在右侧看到一个 unsigned int 值,在左侧看到一个 unsigned int * 类型的指针,它会尝试隐式执行相同的转换.但由于这是一种危险且可能毫无意义的转换,编译器会对此发出警告。我的说

warning: assignment makes pointer from integer without a cast

但是当你写一个显式转换时,对于大多数编译器来说这意味着,"trust me, I know what I'm doing, do this conversion and keep your doubts to yourself, I don't want to hear any of your warnings."

最后,

printf("%s", (char*)string);

这里我们做了两件事。首先,我们显式地将 unsigned int * 指针转换为 char * 指针。这也是一个有问题的转换,但关注度要低得多。在当今绝大多数计算机上,所有指针(无论它们指向什么)都具有相同的大小和表示形式,因此像这样的转换不太可能导致任何问题。

然后我们做的第二件事是,最后,尝试使用 printf%s 打印 char * 指针。正如您所发现的,它并不总是有效。它在我的电脑上也不适合我。

有它可以工作的计算机,所以你的问题 "Is it possible to do it?" 的答案是 "Yes, maybe, but."

为什么它对你不起作用?我不能确定,但​​这可能是出于同样的原因,它对我不起作用。在我的机器上,指针是 64 位的,但常规整数(包括 `unsigned int)是 32 位的。所以当我们调用

expects_unsigned_int("some text");

并试图将一个指针插入一个 int 大小的槽中,我们刮掉了它的 64 位中的 32 位。那是一个信息丢失的转换,所以它很可能是一个不可恢复的错误。

让我们打印一些额外的信息,这样我们就可以确认这是怎么回事。我鼓励您在计算机上对您的程序进行这些修改,这样您就可以看到您得到的结果。

让我们这样重写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)。另外,我正在制作 char * 类型的 string,因为没有必要 unsigned int *.

当我运行修改程序时,我得到的是:

string = 0x101295f20 = some text
int: 4, pointer: 8
val = 1295f20
string = 0x1295f20
Segmentation fault: 11

我们立即看到几件事:

  • 在这台机器上指针比整数大(正如我之前所说)。我们不可能在不丢失数据的情况下将指针填充到 int 中。
  • 我们确实刮掉了刺痛指针的一些位。它开始是 101295f20,最后是 1295f20
  • 该程序无法运行。它因分段违规而崩溃,可能是因为损坏的指针值 0x1295f20 指向其地址 space.
  • 之外

那么我们该如何解决这个问题呢?最好的方法是 而不是 尝试通过旨在容纳整数的插槽传递指针值。

或者,如果我们真的想要,如果我们被绑定并决定将指针转换为整数并再次返回,我们可以尝试使用更大的整数,例如 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 中成功恢复,并且字符串打印正确.

但是,最后,请找出更好的方法!