将char*转换为unsigned char*

22

在C语言中,如何正确地将char*复制到unsigned char*?以下是我的代码:

int main(int argc, char **argv)
{
    unsigned char *digest;

    digest = malloc(20 * sizeof(unsigned char));
    strncpy(digest, argv[2], 20);
    return 0;
}

我想要正确地将char* 数组复制到unsigned char* 数组。使用上述代码,我得到以下警告:

warning: pointer targets in passing argument 1 of âstrncpyâ differ in signedness 

编辑:添加更多信息,我的要求是调用者在命令行上以字符串形式向主函数提供SHA摘要,并且主函数在内部将其保存在摘要中。 SHA摘要可以使用无符号字符最好的方式来表示。

现在问题是,我不能改变主函数(** char)的签名,因为主函数解析其他参数时需要char*而不是unsigned char*。


2
哈希摘要通常表示为摘要的十六进制值的ASCII表示形式(例如“b6379dab2c...”)。对于此操作,使用char类型完全可以。 - Oliver Charlesworth
@oli 所以基本上,即使没有任何问题,强制转换也应该能够正常工作 strncpy((char*)digest, argv[2], 20); 因为我们处理的是ASCII码? - Rajiv
@Rajiv:表示 SHA-1 摘要的方式有两种,它是 160 位。其中一种方式是使用 20 个 8 位字节,而“unsigned char”是最好的类型。另一种方式是使用 ASCII 表示法,在该表示法中,每个字符都是一个十六进制数字,代表 4 位,因此需要 40 个字符。显然,“strncpy”无法在它们之间进行转换。 - Steve Jessop
@Steve:是的,我正在使用带有20个8位的无符号字符版本。如果strncpy不能用,那么memcpy或任何其他函数能完成这个任务吗? - Rajiv
1
@Rajiv:你认为用户会如何在终端上输入这些8位值?如果其中一个是0怎么办? - Steve Jessop
7个回答

15
为避免编译器警告,你只需要:
strncpy((char *)digest, argv[2], 20);

避免编译器警告通常不是一个好主意;它告诉你存在根本性的不兼容性。在这种情况下,不兼容性在于char的范围为-128到+127(通常情况下),而unsigned char的范围为0到+255。


是的,那就是问题所在,我怎样更好地解决这种不兼容性呢? - Rajiv
如果您能告诉我们为什么需要将其作为无符号字符传递,那可能会帮助我们回答您的问题。猜测一个更好的解决方案,您可以考虑使用结构体或联合体,而不是一大块无符号字符内存。 - noelicus
char *unsigned char * 的情况下,警告(根据标准,编译器应将其视为错误!)很少表示任何错误,除非是标准中的错误。 几乎所有标准函数都使用 char *,但处理的数据实际上被视为 unsigned char 数组。 例如,参见 strcmp - R.. GitHub STOP HELPING ICE
@R..:你说的“被当作一个unsigned char数组”是什么意思? - Oliver Charlesworth
我以 strcmp 作为例子。它需要基于第一个不匹配的字节之间的差异进行比较,*将其解释为 unsigned char*。 - R.. GitHub STOP HELPING ICE
还有一个问题是,在非二进制补码实现中,如果char是有符号的,那么值0可能有两种表示方式,而只有所有位都为0的字节才是空终止符。这意味着在这样的实现中处理空终止字符串的任何函数必须将它们作为unsigned char []来处理以区分它们。不过,必须承认,任何使用带符号的普通char的非二进制补码实现本身就是相当愚蠢的... - R.. GitHub STOP HELPING ICE

6

由于类型不同,你无法正确拷贝它,编译器正是因为这个原因警告你。

如果你需要拷贝argv[2]数组的原始位,请使用memcpy函数。


使用 memcpy 函数时,首先需要检查 argv[2] 的长度,以避免访问数组外的元素。 - pmg

2
strncpy()调用中去除符号。
strncpy((char*)digest, argv[2], 20);

或者引入另一个变量。
#include <stdlib.h>
#include <string.h>

int main(int argc, char **argv)
{
    unsigned char *digest;
    void *tmp;                   /* (void*) is compatible with both (char*) and (unsigned char*) */

    digest = malloc(20 * sizeof *digest);
    if (digest) {
        tmp = digest;
        if (argc > 2) strncpy(tmp, argv[2], 20);
        free(digest);
    } else {
        fprintf(stderr, "No memory.\n");
    }
    return 0;
}

请注意,malloc(20 * sizeof(unsigned char*))可能不是你想要的。我认为你想要的是malloc(20 * sizeof(unsigned char)),或者,按照定义,sizeof(unsigned char)1,所以你可以使用malloc(20)。 如果你真的想在调用中使用每个元素的大小,请像上面我的代码那样使用对象本身。

1
在我看来,这里引入一个虚拟变量只会使代码更加晦涩,没有相应的好处。 - Oliver Charlesworth
OP 显然想要一个“比强制转换更好的方法”。混淆的 (void*) 变量实现了一种不同的方式:我会把是否更好的决定留给 OP(像你一样,@Oli,我认为它并不是更好的方法)。 - pmg

1
没有一种通用的方法将char *转换为unsigned char *。它们指向数据,您必须了解数据的格式。
至少有3种不同的SHA-1哈希格式:
  • 作为恰好20个八位字节的数组的原始二进制摘要
  • 作为十六进制字符串的摘要,例如"e5e9fa1ba31ecd1ae84f75caaa474f3a663f05f4"
  • 作为Base64字符串的摘要,例如"5en6G6MezRroT3XKqkdPOmY/BfQ="
您的malloc(20 * sizeof(unsigned char))具有二进制摘要的确切大小,但太小无法容纳十六进制字符串或Base64字符串。我猜unsigned char *指向一个二进制摘要。
但是char *来自main()的命令行参数,所以char *可能指向一个字符串。命令行参数始终是C字符串;它们以NUL终止符'\0'结尾,并且字符串中从不包含'\0'。原始二进制摘要可能包含'\0',因此它们不能作为命令行参数使用。
将SHA-1摘要从十六进制字符串转换为原始二进制的代码可能如下:
#include <stdio.h>
#include <stdlib.h>

unsigned char *
sha1_from_hex(char *hex)
{
    int i, m, n, octet;
    unsigned char *digest;

    digest = malloc(20);
    if (!digest)
        return NULL;

    for (i = 0; i < 20; i++) {
        sscanf(hex, " %n%2x%n", &m, &octet, &n);
        if (m != 0 || n != 2)
            goto fail;
        digest[i] = octet;
        hex += 2;
    }
    if (*hex)
        goto fail;
    return digest;

fail:
    free(digest);
    return NULL;
}

不要使用 strncpy(dst, src, 20) 复制原始二进制摘要。strncpy(3) 函数在找到 '\0' 后停止复制;因此,如果您的摘要包含 '\0',则会丢失部分摘要。

1
只需在其前面加上(char*)(unsigned char*)

1

您可以这样使用memcpy:

memcpy(digest, argv[2], strlen(argv[2]) + 1);

由于此函数指向的源指针和目标指针所指对象的基础类型并不相关,因此。


您无法保证访问argv[2][19]是被允许的。 - pmg
我不确定在 digest 中是否需要 '\0'。无论如何,现在你需要检查 strlen(argv[2]) 是否小于为 digest 分配的空间大小 :) - pmg
@pmg 嗯...那么OP必须将摘要的分配大小与(strlen(argv [2])+1)* sizeof(unsigned char)同步。 - cyber_raj
在一些奇怪的机器上,sizeof(char)可能不是1。例如TMS320C40。这里显露了我的年龄。 - quickly_now

0

警告就是它所说的,你正在将一个 unsigned char * digest 传递给 strncpy 函数,而它的符号与它期望的不同。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接