【问题标题】:Create a string from uint32/16_t and then parse back the original numbers从 uint32/16_t 创建一个字符串,然后解析回原始数字
【发布时间】:2012-07-10 16:01:15
【问题描述】:

我需要将一些 uint32_t 和 uint16_t 数字放入 char* 中。然后我需要将它们从缓冲区中取回。

我已经阅读了一些问题,并尝试使用 sprintf 将它们放入 char* 中,然后 sscanf 再次获取原始数字。但是,我无法正确获取它们。

这是我的代码示例,其中只有 2 个数字。但我需要超过 2 个,这就是我使用 realloc 的原因。另外,我不知道如何正确使用 sprintf 和 sscanf 与 uint16_t

uint32_t gid = 1100;
uint32_t uid = 1000;
char* buffer = NULL;
uint32_t offset = 0;

buffer = realloc(buffer, sizeof(uint32_t));
sprintf(buffer, "%d", gid);
offset += sizeof(uint32_t);

buffer = realloc(buffer, sizeof(uint32_t) + sizeof(buffer));
sprintf(buffer+sizeof(uint32_t), "%d", uid);

uint32_t valorGID;
uint32_t valorUID;

sscanf(buffer, "%d", &valorGID);
buffer += sizeof(uint32_t);
sscanf(buffer, "%d", &valorUID);

printf("ValorGID %d ValorUID %d \n", valorGID, valorUID);

我得到的是

ValorGID 11001000 ValorUID 1000 

我需要得到的是

ValorGID 1100 ValorUID 1000

我是 C 的新手,所以任何帮助都将不胜感激。

【问题讨论】:

    标签: c char printf scanf uint32-t


    【解决方案1】:
    buffer = realloc(buffer, sizeof(uint32_t));
    sprintf(buffer, "%d", gid);
    offset += sizeof(uint32_t);
    
    buffer = realloc(buffer, sizeof(uint32_t) + sizeof(buffer));
    sprintf(buffer+sizeof(uint32_t), "%d", uid);
    

    这真的没有意义,除非在幸运的情况下,否则不会按预期工作。

    让我们假设通常的CHAR_BIT == 8 成立,所以sizeof(uint32_t) == 4。此外,让我们假设int 是一个以二进制补码表示的有符号 32 位整数,没有填充位。

    sprintf(buffer, "%d", gid)gid 的位模式的十进制字符串表示形式解释为int 到缓冲区。在上述假设下,gid 被解释为介于 -2147483648 和 2147483647 之间的数字。因此,十进制字符串表示可能包含'-',包含 1 到 10 位数字和 0 终止符,总共使用 2 到 12 个字节。但是您只分配了四个字节,所以每当999 < gid < 2^32-99(带符号的二进制补码解释为> 999< -99)时,sprintf 写入超过分配的缓冲区大小。

    这是未定义的行为。

    它可能不会立即崩溃,因为分配四个字节通常会有效地为您提供更大的内存块(例如,如果 malloc 总是返回 16 字节对齐的块,则分配的四个字节后面的 12 个字节不能被其他部分使用程序,但属于程序的地址空间,写入它们可能不会被检测到)。但是当分配的块的末尾位于页面边界上时,它很容易崩溃。

    此外,由于您为后续的sprintfs 将写入偏移量提前了四个字节,如果字符串表示(不包括 0 终止符)使用了超过四个字节(而程序没有),则前一个数字的一​​部分将被覆盖t 由于写入未分配的内存而崩溃)。

    线

    buffer = realloc(buffer, sizeof(uint32_t) + sizeof(buffer));
    

    包含更多错误。

    1. buffer = realloc(buffer, new_size); 丢失对分配内存的引用,如果realloc 失败,则会导致泄漏。使用临时的并检查是否成功

      char *temp = realloc(buffer, new_size);
      if (temp == NULL) {
          /* reallocation failed, recover or cleanup */
          free(buffer);
          exit(EXIT_FAILURE);
      }
      /* it worked */
      buffer = temp;
      /* temp = NULL; or let temp go out of scope */
      
    2. 新分配的新大小sizeof(uint32_t) + sizeof(buffer) 始终相同,sizeof(uint32_t) + sizeof(char*)。这通常是 8 个或 12 个字节,因此在分配的区域之外写入并不会导致崩溃或内存损坏(这可能会在很久以后导致崩溃)。

    必须跟踪分配给buffer 的字节数并使用它来计算新的大小。没有(可移植的¹)方法来确定从指针到其开始分配的内存块的大小。


    现在的问题是您是否要在缓冲区中存储字符串表示形式或位模式。

    存储字符串表示存在字符串表示的长度随值变化的问题。因此,您需要在数字表示之间包含分隔符,或者在必要时通过填充(使用空格或前导零)确保所有表示具有相同的长度。例如,这将像

    #include <stdint.h>
    #include <inttypes.h>
    
    #define MAKESTR(x) # x
    #define STR(x) MAKESTR(x)
    
    /* A uint32_t can use 10 decimal digits, so let each field be 10 chars wide */
    #define FIELD_WIDTH 10
    
    uint32_t gid = 1100;
    uint32_t uid = 1000;
    
    size_t buf_size = 0, offset = 0;
    char *buffer = NULL, *temp = NULL;
    buffer = realloc(buffer, FIELD_WIDTH + 1); /* one for the '\0' */
    if (buffer == NULL) {
        exit(EXIT_FAILURE);
    }
    buf_size = FIELD_WIDTH + 1;
    sprintf(buffer, "%0" STR(FIELD_WIDTH) PRIu32, gid);
    offset += FIELD_WIDTH;
    
    temp = realloc(buffer, buf_size + FIELD_WIDTH);
    if (temp == NULL) {
        free(buffer);
        exit(EXIT_FAILURE);
    }
    buffer = temp;
    temp = NULL;
    buf_size += FIELD_WIDTH;
    sprintf(buffer + offset, "%0" STR(FIELD_WIDTH) PRIu32, uid);
    offset += FIELD_WIDTH;
    /* more */
    
    uint32_t valorGID;
    uint32_t valorUID;
    
    /* rewind for scanning */
    offset = 0;
    
    sscanf(buffer + offset, "%" STR(FIELD_WIDTH) SCNu32, &valorGID);
    offset += FIELD_WIDTH;
    sscanf(buffer + offset, "%" STR(FIELD_WIDTH) SCNu32, &valorUID);
    
    printf("ValorGID %u ValorUID %u \n", valorGID, valorUID);
    

    使用零填充的固定宽度字段。如果您宁愿使用分隔符而不是固定宽度,则所需长度和偏移量的计算会变得更加复杂,但除非数字很大,否则会占用更少的空间。

    如果您希望存储位模式,这将是最紧凑的存储方式,您可以使用类似

    size_t buf_size = 0, offset = 0;
    unsigned char *buffer = NULL, temp = NULL;
    buffer = realloc(buffer, sizeof(uint32_t));
    if (buffer == NULL) {
        exit(EXIT_FAILURE);
    }
    buf_size = sizeof(uint32_t);
    for(size_t b = 0; b < sizeof(uint32_t); ++b) {
        buffer[offset + b] = (gid >> b*8) & 0xFF;
    }
    offset += sizeof(uint32_t);
    
    temp = realloc(buffer, buf_size + sizeof(uint32_t));
    if (temp == NULL) {
        free(buffer);
        exit(EXIT_FAILURE);
    }
    buffer = temp;
    temp = NULL;
    buf_size += sizeof(uint32_t);
    for(size_t b = 0; b < sizeof(uint32_t); ++b) {
        buffer[offset + b] = (uid >> b*8) & 0xFF;
    }
    offset += sizeof(uint32_t);
    
    /* And for reading the values */
    uint32_t valorGID, valorUID;
    
    /* rewind */
    offset = 0;
    valorGID = 0;
    for(size_t b = 0; b < sizeof(uint32_t); ++b) {
        valorGID |= buffer[offset + b] << b*8;
    }
    offset += sizeof(uint32_t);
    valorUID = 0;
    for(size_t b = 0; b < sizeof(uint32_t); ++b) {
        valorUID |= buffer[offset + b] << b*8;
    }
    offset += sizeof(uint32_t);
    

    ¹如果您知道malloc 等在您的实现中是如何工作的,则可以从malloc 的簿记数据中找到大小。

    【讨论】:

    • 非常感谢您的反馈。正如我所说,我是 C 的新手。我不知道我必须检查 realloc 是否失败。第一个解决方案,字符串表示,是我需要的。我会实施它。再次感谢您的宝贵时间!
    • 不客气。我希望你没有得到我在责备你的印象,关键是在 C 语言中程序员必须进行错误检查,没有很好的异常可以告诉你哪一行代码出了什么问题。当然,检查malloc/realloc 的失败很有可能永远不会检测到失败。但有一次,你会很高兴拥有它。
    【解决方案2】:

    格式说明符'%d' 用于int,因此对于uint32_t 是错误的。首先uint32_t 是一个无符号类型,所以你至少应该使用'%u',但它的宽度也可能与intunsigned 不同。标准中预见了宏:PRIu32 用于printfSCNu32 用于scanf。举个例子:

    sprintf(buffer, "%" PRIu32, gid);
    

    【讨论】:

    • 我可以用那个宏来打印和其他功能吗?谢谢!
    • 当然,还有一大堆用于其他类型和说明符的类似宏。
    【解决方案3】:

    sprintf 返回的表示是一个 char*。如果您尝试将整数数组存储为其字符串表示形式,那么您的基本数据类型是 char**。如果我们只存储字符串数据本身,这是一个不规则的 char 矩阵,但是由于 uint32_t 可以产生的最长字符串是 10 个字符,加上一个用于终止 null 的字符,因此预先分配这么多字节来保存每个字符是有意义的细绳。

    所以要将数组 a 中的 n 个 uint32_t 作为字符串存储在数组 s 中:

    const size_t kMaxIntLen=11;
    
    uint32_t *a,b;
    // fill a somehow
    ...
    
    size_t n,i;
    char **s.*d;
    
    if((d=(char*)malloc(n*kMaxIntLen))==NULL)
       // error!
    if((s=(char**)malloc(n*sizeof(char*)))==NULL)
       // error!
    for(i=0;i<n;i++)
        {
        s[i]=d+i; // this is incremented by sizeof(char*) each iteration
        snprintf(s[i],kMaxIntLen,"%u",a[i]); // snprintf to be safe
        }
    

    现在第 i 个数字位于 s[i],因此打印它只是 printf("%s",s[i]);,将其作为整数检索到 bsscanf(s[i],"%u",&amp;b);

    后续的内存管理有点棘手。与其不断使用realloc() 来增加缓冲区,不如预先分配一块内存并仅在耗尽时更改它。如果realloc() 失败,它会返回NULL,所以在调用它之前存储一个指向主缓冲区的指针,这样你就不会丢失对数据的引用。首先重新分配 d 缓冲区 - 再次为更多字符串分配足够的空间 - 然后如果成功,请查看 d 是否已更改。如果是这样,请销毁(free()s 缓冲区,再次malloc() 并重建索引(您必须这样做,因为如果d 已更改所有索引都已过时)。如果没有,realloc()s 并修复新索引。我建议将整个事情包装在一个结构中,并有一组例程对其进行操作,例如:

    typedef struct StringArray
    {
    char **strArray;
    char *data;
    size_t nStrings;
    } StringArray;
    

    这是很多工作。你使用C吗?作为带有istringstream 类和push_back() 容器方法的C++ STL vector&lt;string&gt;list&lt;string&gt;,这要容易得多。

    【讨论】:

    • 是的,我正在编写一个软件来在 ext2 FS 中进行操作,作为大学练习。这是我们第一次使用 C。
    【解决方案4】:
    uint32_t gid = 1100;
    uint32_t uid = 1000;
    char* buffer = NULL;
    uint32_t offset = 0;
    
    buffer = realloc(buffer, sizeof(uint32_t));
    sprintf(buffer, "%d", gid);
    offset += sizeof(uint32_t);
    
    buffer = realloc(buffer, sizeof(uint32_t) + sizeof(buffer));
    sprintf(buffer+sizeof(uint32_t), "%d", uid);
    
    uint32_t valorGID;
    uint32_t valorUID;
    
    sscanf(buffer, "%4d", &valorGID);
    buffer += sizeof(uint32_t);
    sscanf(buffer, "%d", &valorUID);
    
    printf("ValorGID %d ValorUID %d \n", valorGID, valorUID);
    

    `

    我认为这可能会解决问题!

    【讨论】:

    • 是的,但我需要添加超过 1 个号码。有时超过 4 位数。
    猜你喜欢
    • 2017-10-22
    • 1970-01-01
    • 2023-03-05
    • 1970-01-01
    • 2011-12-18
    • 2020-09-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多