分享
为什么问答平台  ›  专栏  ›  技术社区  ›  netanel94

它有什么意义?为什么SScanf函数仍然工作? - How does it make sense and why is the sscanf function still working?

  •  2
  • netanel94  · 技术社区  · 10 月前

    如您所见,我只分配了1个字节作为 sizeof(char) sscanf() 读取整个块直到空白 string_of_letters . 这怎么可能?

    什么是定义 sSCAN() ?

    例如: str = "rony is a man" 但在 字符串 位置 i 我看到了“罗尼”。

    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]);
    }
    
    2 回复  |  直到 10 月前
        1
  •  4
  •   Jonathan Leffler    10 月前

    有很多方法可以修复显示的代码片段。此代码显示其中三个。如问题注释中所述,您需要在循环中分配至少2个字符(因为 %[…] 扫描集创建以空结尾的字符串),但可以使用 %1[^, ] 转换为一次获得一个字符。请注意,您需要测试 sscanf() 检查你是否达到了你的期望。您还需要增加读取次数,以避免反复读取同一个字符。在更一般的情况下,你会使用 %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;
    }
    

    显示的代码在 Valgrind 在Mac上运行的MacOS 10.13.6 high Sierra和Valgrind 3.14.0.git(从git中提取的版本,而不是正式发布的源代码集)。

    输出:

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

    正如已经观察到的,问题Sorta中的代码工作,更多是出于偶然而非设计。指针由返回 malloc() 被约束,以便它指向可用于任何目的的内存位置:

    C11 §7.22.3 Memory management functions

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

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

        2
  •  6
  •   mnistic    10 月前

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

    在像你这样的小玩具程序中,由于缓冲区足够小,而且没有其他事情发生,所以它看起来工作正常并不奇怪。然而,在一个更大的项目中,很可能 sSCANF 会将传入的缓冲区结尾写入另一个缓冲区,为其他内容分配缓冲区,更改不想更改的内存,或者,如果幸运的话,写入受保护的内存,从而导致访问冲突。