【问题标题】:Scansets in sscanf with string delimiters带有字符串分隔符的 sscanf 中的扫描集
【发布时间】:2014-02-20 14:43:49
【问题描述】:

我有一个 ASCII 字符串,格式为:

00001\x02This is a string\x030000100001\x021.0\x03\x021.0\x03\x021.0\x03\x021.0\x03001

在描述性方面,它是一个带有 5 位前导零整数的字符串,一个由 STX 和 ETX ASCII 字符封装的字符串,另外 2 个 5 位前导零整数,4 个由 STX 和 ETX ASCII 字符封装的浮点值由 3 位前导零整数。我正在尝试使用 sscanf 来解析字符串。它的行为不应该如此。我正在使用以下格式字符串:

"%5hu\x02%[0-9a-zA-Z _-]s\x03%5hu%05hu\x02%lf\x03\x02%lf\x03\x02%lf\x03\x02%lf\x03%3hhu"

我尝试将扫描集更改为不同的值,包括[^\x03]。我还尝试添加和删除长度说明符。我很确定这是导致问题的扫描集。我想知道 STX 和 ETX 字符文字是否有困难。任何人都知道为什么这不起作用?还是使用纯 C89 的更好选择?谢谢。

对于那些想要完整代码测试的人:

unsigned short one;
char two[32];
unsigned short three, four;
double five, six, seven, eight;
unsigned char nine;
char temp[] = "00001\x02This is a string\x03""0000100002\x02""1.1\x03\x02""2.2\x03\x02""3.3\x03\x02""4.5\x03""003";
sscanf(temp, "%5hu\x02%[-0-9a-zA-Z _]s\x03%5hu%05hu\x02%lf\x03\x02%lf\x03\x02%lf\x03\x02%lf\x03%3hhu",
       &one, two, &three, &four, &five, &six, &seven, &eight, &nine);

【问题讨论】:

  • 注意:八进制常量限制在反斜杠后最多 3 位,但十六进制常量不限于两个或三个十六进制数字,所以 \x0300000100001 都是单个字符。我不确定这是否是您的问题的一部分。
  • 另外,请注意扫描集是独立存在的;它不是s 转换说明符的限定符。您的格式字符串在扫描集匹配后的数据中查找实际的s
  • 已编辑以提供完整的代码示例。我知道十六进制转义问题。在上面的示例中,我将字符串分解以防止十六进制转义使用太多字符。我认为s 约定是可选的。我在没有它的情况下进行了测试,得到了相同的结果(或同样糟糕的结果)。
  • 查看我的工作代码改编自您的原始格式字符串(通过删除 s 和拆分),以及与您的原始字符串等效的数据字符串(所有数字都是 1,而不是变化)。您没有显示来自sscanf() 的返回值的测试。例如,您的sscanf() 是否存在无法识别%hhu 的危险?它被添加到 C99 中,但例如,Microsoft 编译器是 C89 编译器,而不是 C99 编译器。你在哪个平台?您使用的是哪个 C 编译器?

标签: c parsing format scanf


【解决方案1】:

我不能立即明白为什么它不起作用,尽管我会将它分解成一个字符串并让它越来越长,直到它失败。

就我个人而言,我会使用strtok(在由STX 和ETX 封装的边界上)解析它,然后使用scanf 读取特定的浮点数和整数。

【讨论】:

  • 从未发现问题,所以我最终使用strtok 将字符串解析为更小的字符串并扫描这些值。不像单个sscanf 调用那样优雅或高效,但在某些情况下点,你只需要前进。
【解决方案2】:

注意:八进制常量被限制在反斜杠后最多 3 位,但十六进制常量不限于两个或三个十六进制数字,所以 \x0300000100001 都是单个字符。

GCC 警告我:

ssss.c:6:1: error: hex escape sequence out of range [-Werror]
 "00001\x02This is a string\x030000100001\x021.0\x03\x021.0\x03\x021.0\x03\x021.0\x03001";
 ^
ssss.c:6:1: error: hex escape sequence out of range [-Werror]

(根据对问题的修改,您已经意识到了这个问题。)

另外,请注意扫描集是独立存在的;它不是s 转换说明符的限定符。您的格式字符串在扫描集匹配的内容之后在数据中查找实际的 s,并且永远不会找到一个,因为扫描集在文字匹配之前吃掉任何 s。这是您的实际问题;删除%[0-9a-zA-Z _-] 扫描集之后的s

此代码有效。请注意data 字符串的明智分解,以便十六进制常量在您希望它们终止的地方终止。格式字符串中的拆分简化了表示。 C 将两个相邻的字符串文字连接在一起,这非常有用。

#include <stdio.h>

int main(void)
{
    char const data[] =
        "00001\x02This is a string\x03" "0000100001\x02" "1.0\x03\x02"
        "1.0\x03\x02" "1.0\x03\x02" "1.0\x03" "001";
    char const format[] =
        "%5hu\x02%[0-9a-zA-Z _-]\x03%5hu%05hu\x02%lf\x03\x02%lf\x03\x02"
        "%lf\x03\x02%lf\x03%3hhu";

    unsigned short i1;
    char s2[20];
    unsigned short i3;
    unsigned short i4;
    double d5;
    double d6;
    double d7;
    double d8;
    unsigned char i9;
    int rc;

    if ((rc = sscanf(data, format, &i1, s2, &i3, &i4, &d5, &d6, &d7, &d8, &i9)) != 9)
        printf("sscanf failed - %d conversions\n", rc);
    else
        printf("i1 = %d; s2 = [%s]; i3 = %d; i4 = %d; d5 = %f;\n"
               "d6 = %f; d7 = %f; d8 = %f; i9 = %d\n",
               i1, s2, i3, i4, d5, d6, d7, d8, i9);
    return 0;
}

样本输出:

i1 = 1; s2 = [This is a string]; i3 = 1; i4 = 1; d5 = 1.000000;
d6 = 1.000000; d7 = 1.000000; d8 = 1.000000; i9 = 1

我在main() 中的return 0 之前添加了这段代码。这在扫描集之后没有s;当我将s 留在原地时,sscanf() 返回值 2,而不是 9。

    unsigned short one;
    char two[32];
    unsigned short three, four;
    double five, six, seven, eight;
    unsigned char nine;
    char temp[] = "00001\x02This is a string\x03""0000100002\x02""1.1\x03\x02""2.2\x03\x02""3.3\x03\x02""4.5\x03""003";
    if ((rc = sscanf(temp, "%5hu\x02%[-0-9a-zA-Z _]\x03%5hu%05hu\x02%lf\x03\x02%lf\x03\x02%lf\x03\x02%lf\x03%3hhu",
           &one, two, &three, &four, &five, &six, &seven, &eight, &nine)) != 9)
        printf("sscanf failed - %d conversions\n", rc);
    else
        printf("i1 = %d; s2 = [%s]; i3 = %d; i4 = %d; d5 = %f;\n"
               "d6 = %f; d7 = %f; d8 = %f; i9 = %d\n",
               one, two, three, four, five, six, seven, eight, nine);

合并后的程序输出为:

i1 = 1; s2 = [This is a string]; i3 = 1; i4 = 1; d5 = 1.000000;
d6 = 1.000000; d7 = 1.000000; d8 = 1.000000; i9 = 1
i1 = 1; s2 = [This is a string]; i3 = 1; i4 = 2; d5 = 1.100000;
d6 = 2.200000; d7 = 3.300000; d8 = 4.500000; i9 = 3

使用 GCC 4.8.2 在 Mac OS X 10.9.1 Mavericks 上进行测试。

【讨论】:

  • 我复制并粘贴了你的代码,没有骰子。我只能得到第一个要扫描和转换的项目。因此,扫描集或 STX/ETX 字符肯定有问题。我正在使用 Microchip 基于 GCC 的所谓 ANSI C89 兼容编译器。
  • 鉴于您提供的反馈,我认为问题出在您的 C 库中(而不是编译器本身,或在您的程序代码中)。请注意,%hhu 转换在 C89 中无效;它是在 C99 中添加的。 (解决方法是使用%hu 转换规范将该值读入unsigned short,然后进行范围验证并将该值分配给您的unsigned char 变量。)AFAICR,其他转换规范在C89 中都应该是有效的.
  • 我开始同意标准 C 库的这种实现。我确实尝试将 %hhu 更改为 %hu 但由于它没有通过扫描集,我认为这不是问题(还没有?)。
  • 在这个阶段,您需要仔细查看编译器和库的文档。在这一点上,没有完全相同的编译器和库可以帮助您的人真的不多。考虑将这些信息添加到问题中。系统可能没有实现扫描集。尝试更改数据字符串(仅用于测试),使字符串后面有一个空格,并将扫描集替换为%s。运行它,看看它是否有效。它应该或应该更好地工作。如果是这样,那么我们可以将矛头指向实现/库;它不支持扫描集。
  • 另一个想法;也许您可以在用sscanf() 解析数据之前用空格替换所有控制字符(\x02\x03)?或者复制数据并替换副本中的控制字符。您可以在复制时检查正确的控制字符是否以正确的顺序出现在数据中。这可能会让您绕过明显有缺陷的sscanf() 实现。记录(仔细)你为什么要做你所做的事情——他们可能会在更高版本的编译器中解决这个问题。将其包含在此代码附近源代码的 cmets 中,而不仅仅是在 VCS 中。
【解决方案3】:

格式说明符太容易弄乱了。考虑将其分解(更易于理解和维护)并检查结果

#define Int5 "%5hu"
// Note:  no 0 ^

#define STX  "\x02"
#define ETX  "\x03"
// Could use hexadecimal constants here as the string is broken up.

#define EncStr STX "%31[0-9a-zA-Z _-]" ETX
// Note:                        no s ^   (@Jonathan comment s is not part of %[]
// String limit      ^

#define FP    STX "%lf" ETX
#define Int3  "%3hhu"

if (9 == sscanf(temp, Int5 EncStr Int5 Int5 FP FP FP FP Int3, 
    &one, two, &three, &four, &five, &six, &seven, &eight, &nine)) Success();

注意:temp 需要分解为十六进制清晰或使用八进制常量

char temp[]  = "00001\x02This is a string\x03" "0000100001\x02" "1.0\x03\x02" 
     "1.0\x03\x02" "1.0\x03\x02" "1.0\x03" "001";
char temp[]  = "00001\002This is a string\0030000100001\0021.0\003\0021.0\003\0021.0\003\0021.0\003001";

【讨论】:

  • 所以,我复制并粘贴了您的代码并再次尝试,结果与下面 Jonathan 的代码相同。它只会解析第一个整数。所以,有些东西对扫描集不满意。我可能不得不重新考虑我的方法并使用strtok 或 abligh 建议的更手动的过程来完成。很遗憾,因为 scanf 功能如此强大。
  • @Psyfun 1) 您是否使用了上述 2 个temp 之一? 2) 你是用"%31[0-9a-zA-Z _-]" 还是别的什么?
  • @Psyfun 我怀疑- 存在编译器问题。试试"%31[0-9a-zA-Z _]"(没有最后-)。 (您使用的是 ASCII?)也许试试 "%31[ -~]"
  • @Psyfun A scanlist 使用- 作为范围不是标准的。 “如果 - 字符在扫描列表中并且不是第一个字符,也不是第一个字符是 ^ 的第二个字符,也不是最后一个字符,则行为是实现定义的” 更多信息:请参阅 stackoverflow.com/questions/13114073/… 建议 "%31[^\003]"
  • 我使用了带有十六进制转义符的临时版本。我将再次尝试使用八进制,但我很确定这工作正常,因为我可以看到数组中每个字符的单个字符代码。我知道- 依赖于实现,并且在开始和结束时都尝试过。我会尝试完全删除它。我确实尝试过%[\x03],但也没有用,但当时可能还有其他问题。我可能还有字符串限定符s
【解决方案4】:

因此,我确定此版本的 C 运行时库中的扫描集已损坏。以下是有效和无效的代码:

unsigned short one;
char two[32];
unsigned short three, four;
double five, six, seven, eight;
unsigned char nine;
int rc1, rc2;

char temp[] = "00001\x02string\x03""0000100002\x02""1.1\x03\x02""2.2\x03\x02""3.3\x03\x02""4.5\x03""003";

char format1[] = "%5hu\x02%[a-z]\x03%5hu%5hu\x02%lf\x03\x02%lf\x03\x02%lf\x03\x02%lf\x03%3hhu";
char format2[] = "%5hu\x02%s\x03%5hu%5hu\x02%lf\x03\x02%lf\x03\x02%lf\x03\x02%lf\x03%3hhu";

rc1 = sscanf(temp, format1, &one, two, &three, &four, &five, &six, &seven, &eight, &nine);   
rc2 = sscanf(temp, format2, &one, two, &three, &four, &five, &six, &seven, &eight, &nine);

在上面的代码中,rc1 返回成功扫描的 1 个项目,rc2 返回显示成功扫描的 9 个项目。因此,我得出的结论是扫描集不能与这种硬件/软件组合正常工作。有人有其他想法或结论吗?感谢所有的帮助。我没有给任何人一个解决方案,但确实给有帮助的答案加分。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-04-05
    • 1970-01-01
    • 1970-01-01
    • 2019-02-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多