是否有sscanf的变体,指针指向输入字符串而不是缓冲区?

时间:2017-09-23 11:39:28

标签: c parsing scanf tokenize

sscanf的工作原理如下:

int main(const int argc, const char *argv[]) {
    char buf1[1024] = {0};
    char buf2[1024] = {0};
    char buf3[1024] = {0};
    char *str = "abc, 123; xyz";
    sscanf(str, "%[^,], %[^;]; %s", buf1, buf2, buf3);
    printf("'%s' '%s' '%s'", buf1, buf2, buf3); // Prints: "'abc' '123' 'xyz'"
    return 0;
}

我想知道是否有一个函数不需要将str的内容复制到缓冲区(buf1, buf2, buf3),也不需要分配任何新的内存。相反,它只是将指针(ptr1, ptr2, ptr3)设置为指向str中的匹配部分,并且null终止匹配后的任何内容。

int main(const int argc, const char *argv[]) {
    char *ptr1 = NULL;
    char *ptr2 = NULL;
    char *ptr3 = NULL;
    char *str = "abc, 123; xyz";
    //
    // str = "abc, 123; xyz\0"
    //
    _sscanf(str, "%[^,], %[^;]; %s", &ptr1, &ptr2, &ptr3);
    //
    // str = "abc\0 123\0 xyz\0"
    //        ^     ^     ^
    //       ptr1  ptr2  ptr3
    //
    printf("'%s' '%s' '%s'", ptr1, ptr2, ptr3); // Prints: "'abc' '123' 'xyz'"

    return 0;
}

我知道可以使用strtok_rregex.h库这样的函​​数,但我认为在可以修改输入字符串的情况下这会更方便。

2 个答案:

答案 0 :(得分:5)

它不是很漂亮,但%n说明符可能用于捕获令牌开头和结尾的索引。错误检查将确保索引和结束值不是-1

#include <stdio.h>

int main(int argc, char *argv[]) {
    int index1 = -1;
    int end1 = -1;
    int index2 = -1;
    int end2 = -1;
    int index3 = -1;
    int end3 = -1;
    char *str = "abc, 123; xyz";
    sscanf(str, " %n%*[^,]%n, %n%*[^;]%n; %n%*s%n", &index1, &end1, &index2, &end2, &index3, &end3);
    printf("'%.*s' '%.*s' '%.*s'", end1, str + index1, end2 - index2, str + index2, end3 - index3, str + index3); // Prints: "'abc' '123' 'xyz'"
    return 0;
}

答案 1 :(得分:1)

没有标准化的变体,最终指向指向原始字符串中位置的char *。 POSIX中有一个变体,它为每个字符串项分配内存并将数据复制到其中。

sscanf()的功能与fscanf()及其他变体的功能相匹配,并且在非常广泛的范围内,适用于所有变体的内容适用于所有变体。但是,您所寻求的内容无法应用于基于文件的变体,因此它不存在。

sscanf()的变体为字符串分配内存。它是sscanf()的POSIX 2008变体,以及m修饰符。

  

[CX]⌦%c%s%[转换说明符应接受可选的赋值分配字符'm',这将导致内存缓冲区被分配给保持转换后的字符串,包括终止空字符。在这种情况下,对应于转换说明符的参数应该是对将接收指向分配的缓冲区的指针的指针变量的引用。系统应分配缓冲区,就像调用了malloc()一样。应用程序应负责在使用后释放内存。如果没有足够的内存来分配缓冲区,则该函数应将errno设置为[ENOMEM]并导致转换错误。如果函数返回EOF,则在函数返回之前,应释放通过此调用使用赋值分配字符“m”成功分配给参数的任何内存。 ⌫

[CX]表示法将此标记为C标准的扩展(因此m修饰符不是标准C的一部分,并且不支持所有地方),并且⌦和⌫符号标记范围扩展名。

因此,如果您的实现支持它(例如Linux; macOS Sierra不支持),有sscanf()的变体将为您分配正确大小的缓冲区,它需要{{1参数。

Linux上的手册页说:

  

可选的'm'字符。这与字符串转换(char **%s%c)一起使用,并使调用者无需分配相应的缓冲区来保存输入:相反,%[分配一个足够大小的缓冲区,并将该缓冲区的地址分配给相应的指针参数,该参数应该是指向scanf()变量的指针(该变量在调用之前不需要初始化)。当不再需要时,调用者应随后释放(3)此缓冲区。

char *

输出:

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    char data[] = "The hills are alive with the sound of music";
    char *w[9];

    if (sscanf(data, "%ms %ms %ms %ms %ms %ms %ms %ms %ms",
               &w[0], &w[1], &w[2], &w[3], &w[4], &w[5], &w[6], &w[7], &w[8]) != 9)
    {
        fprintf(stderr, "Oops!\n");
        return 1;
    }
    printf("Forwards: %s\n", data);
    printf("Reversed:");
    for (int i = 8; i >= 0; i--)
        printf(" %s", w[i]);
    putchar('\n');
    for (int i = 0; i < 9; i++)
        free(w[i]);
    return 0;
}