【问题标题】:How does it make sense and why is the sscanf function still working?它有什么意义,为什么 sscanf 函数仍然有效?
【发布时间】:2018-08-26 18:43:30
【问题描述】:

如您所见,我只在循环内分配了 1 个字节作为 sizeof(char),而 sscanf() 仍然读取整个块,直到空白进入 string_of_letters。这怎么可能?

sscanf()的定义是什么?

例如:str = "rony is a man" 但在string_of_letters 位置i 我看到“rony”。

char **string_of_letters;
int i;
char *read = str;

string_of_letters = (char**)malloc(3 * sizeof(char*));
for (i = 0; i < 3; i++) {
    string_of_letters[i] = (char*)malloc(sizeof(char));
    sscanf(read,"%[^, ]", &(*string_of_letters[i]));
    printf("%s\n", string_of_letters[i]);
}

【问题讨论】:

  • 没有过多阅读您的问题的详细信息,如果您所说的一切都是真的,您为 1 个字符分配了足够的空间,但将一个完整的字符串读入其中(超过 1 个字符),您很可能只是覆盖了该单个字符之后的记忆。这可能会起作用,因为在当今的内存架构中,您实际上不能只分配 1 个字符,但也可能会产生不利影响。
  • 我喜欢阅读 POSIX site 的函数定义。在那里阅读sscanf()
  • 正如旁注&amp;(*string_of_letters[i]) 与更简单的string_of_letters[i] 相同
  • 您需要在循环内分配至少 2 个字符(因为 %[…] 扫描集创建一个以空字符结尾的字符串),但是您可以使用 %1[^, ] 作为转换来获得一个字符一次。请注意,您需要测试 sscanf() 的返回值以检查您是否得到了预期的结果。您还需要增加 read 以免一遍又一遍地读取相同的字符。在更一般的情况下,您会使用%n 来告知扫描停止的位置(请参阅Using sscanf() in a loop)。扫描集不跳过空格(%c%n 也不跳过)。

标签: c scanf


【解决方案1】:

C 不强制执行运行时内存边界检查,因此您只分配一个字节的事实对sscanf 的函数没有影响:它会很乐意将整个字符串存储到由指向的内存位置您提供的指针。但是,如果缓冲区不够大,则结果是未定义的行为,其确切后果取决于要考虑的太多因素(使用的编译器及其版本、操作系统、内存的当前状态等)。

在像您这样的小型玩具程序中,它似乎可以正常工作并不奇怪,因为缓冲区足够小并且没有太多其他事情发生。然而,在更大的程序中,sscanf 很可能会覆盖传入缓冲区的末尾并写入另一个缓冲区,分配给其他东西,改变你不想改变的内存,或者,如果你很幸运例如,进入受保护的内存,导致访问冲突。

【讨论】:

    【解决方案2】:

    有很多方法可以修复显示的代码片段。这段代码显示了其中的三个。如问题的 cmets 所述,您需要在循环内分配至少 2 个字符(因为 %[…] 扫描集创建一个以 null 结尾的字符串),但是您可以使用 %1[^, ] 作为转换来获得一个字符一次。请注意,您需要测试 sscanf() 的返回值以检查您是否得到了预期的结果。您还需要增加 read 以免一遍又一遍地读取相同的字符。在更一般的情况下,您会使用%n 来告知扫描停止的位置(请参阅Using sscanf() in a loop)。扫描集不会跳过空格(%c%n 也不会——所有其他标准转换都会跳过前导空格,包括换行符)。

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    enum { LIST_SIZE = 3 };
    
    static void free_array(size_t n, char **arr)
    {
        for (size_t i = 0; i < n; i++)
            free(arr[i]);
        free(arr);
    }
    
    int main(void)
    {
        char str[] = "rony is a man";
        char **string_of_letters;
        char *read = str;
    
        printf("Variant 1:\n");
        string_of_letters = (char **)malloc(LIST_SIZE * sizeof(char *));
        for (int i = 0; i < LIST_SIZE; i++)
        {
            string_of_letters[i] = (char *)malloc(2 * sizeof(char));
            if (sscanf(&read[i], "%1[^, ]", string_of_letters[i]) != 1)
                printf("Conversion failed on %d\n", i);
            else
                printf("%s\n", string_of_letters[i]);
        }
    
        free_array(LIST_SIZE, string_of_letters);
    
        printf("Variant 2:\n");
        string_of_letters = (char **)malloc(LIST_SIZE * sizeof(char *));
        for (int i = 0; i < LIST_SIZE; i++)
        {
            string_of_letters[i] = (char *)malloc(sizeof(char));
            *string_of_letters[i] = read[i];
            printf("%c\n", *string_of_letters[i]);
        }
    
        free_array(LIST_SIZE, string_of_letters);
    
        printf("Variant 3:\n");
        strcpy(str, "  r o  n");
    
        char char_list[LIST_SIZE + 1];      // NB: + 1 provides space for null byte
        int offset = 0;
        for (int i = 0; i < LIST_SIZE; i++)
        {
            int pos;
            printf("Offset = %d: ", offset);
            if (sscanf(&read[offset], " %1[^, ]%n", &char_list[i], &pos) != 1)
            {
                printf("Conversion failed on character index %d\n", i);
                break;
            }
            else
                printf("%c\n", char_list[i]);
            offset += pos;
        }
    
        return 0;
    }
    

    显示的代码在运行 macOS 10.13.6 High Sierra 和 Valgrind 3.14.0.GIT(从 Git 中提取的版本,而不是正式发布的一组源代码)的 Mac 上在 Valgrind 下干净地运行。

    输出:

    Variant 1:
    r
    o
    n
    Variant 2:
    r
    o
    n
    Variant 3:
    Offset = 0: r
    Offset = 3: o
    Offset = 5: n
    

    正如已经观察到的那样,问题中的代码可以正常工作,更多的是偶然而不是设计。 malloc() 返回的指针受到约束,因此它指向可用于任何目的的内存位置:

    C11§7.22.3 Memory management functions

    ¶1 ... 如果分配成功则返回的指针经过适当对齐,以便可以分配给 指向具有基本对齐要求的任何类型对象的指针,然后使用 访问分配的空间中的此类对象或此类对象的数组(...)。 …

    这意味着由于其他类型的对齐要求,单个char 的连续分配将不连续。通常,您会发现分配的最小空间为 8 或 16 字节(在 32 位或 64 位平台上),但这绝不是必需的。这确实意味着分配的空间通常比您请求的要多(特别是如果您请求单个字节)。但是,访问该额外空间会导致未定义的行为。您对示例代码的运行表明,有时“未定义行为”的行为或多或少符合预期。

    【讨论】:

      猜你喜欢
      • 2020-08-02
      • 2020-09-09
      • 1970-01-01
      • 2011-09-28
      • 1970-01-01
      • 2015-12-01
      • 1970-01-01
      • 2020-04-13
      • 2012-10-06
      相关资源
      最近更新 更多