strncmp的正确使用方法

10

背景简介:我有一个客户端和一个服务器程序,它们通过Unix套接字相互通信。在服务器端解析接收到的消息时,我试图使用strncmp来确定要执行的操作。

我的问题是如何准确地确定strncmp的长度参数。这个问题是有问题的,因为我的一些消息共享一个公共前缀。例如,我有一个消息“getPrimary”,它会导致服务器响应主服务器地址,还有一个消息“getPrimaryStatus”,它会导致服务器响应主服务器的状态。我的最初想法是这样做:

if(strncmp(message,"getPrimary",strlen("getPrimary"))==0){
    return foo;
}
else if(strncmp(message,"getPrimaryStatus",strlen("getPrimaryStatus"))==0){
    return bar;
}
这个问题在于,当我向服务器发送"getPrimaryStatus"时,代码将始终返回foo,因为strncmp没有检查字符串足够远的位置。我可以将strlen(message)作为长度参数传递给strncmp,但这似乎违背了使用strncmp的目的,即防止意外输入导致的溢出。我有一个静态变量来存储最大消息长度,但似乎将其作为长度传递仅确保如果消息溢出,则效果最小化。
我想出了一些解决方案,但它们不是很优美,所以我想知道是否有常见的方法来处理这个问题。
供参考,我的当前解决方案是: 按照降序长度的顺序对具有共同前缀的任何消息进行if/else if语句排序(这似乎是向代码中添加内容的人扔地雷的好方法)。
将具有共同前缀的消息分组并先查找后缀:
if(strncmp(message,"getPrimary",strlen("getPrimary"))==0){
    if(strncmp(message,"getPrimaryStatus",strlen("getPrimaryStatus"))==0){
        return bar;
    else
        return foo;
    }
}

但这种方式感觉很混乱,特别是我有大约20个不同的可能消息需要处理。

创建一个包含我所有可能消息的数组,在我的初始化序列中添加一个函数来按降序排序数组,并让我的代码在该列表的元素中查找直到找到匹配项。这似乎很复杂和愚蠢。

看起来这应该是一个常见的问题,应该有解决方案,但我到目前为止还没有找到任何有用的信息。

提前谢谢您的帮助!

8个回答

5
假设message字符串应该以空字符结尾,使用strncmp()而不是strcmp()的唯一原因是为了防止在message没有以空字符结尾的情况下查看其末尾内容。

因此,您传递给strncmp()n应该是message接收到的大小,您应该知道它(从读取消息的read() / recv()函数的返回值)。

看起来这是最简单的解决方案。这实际上是我第一次使用Unix套接字来促进进程间通信,而且我大约两个月前编写了套接字处理代码...我完全忘记了我正在使用read(),它返回读取的字节数! - Jordan Wills
1
我认为仅将消息大小传递给strncmp是您的问题的不完整解决方案。请查看我的解决方案以获取详细信息。 - Will Brode
字符串之间有许多匹配字符的比较可以使用“分块”方法最有效地执行,而在早期不同的字符串之间进行比较可能更适合使用逐个字符的方法。如果“n”很大,则某些strncmp实现可能会使用分块,尽管最好为匹配可能性与不可能性的用例分别提供单独的函数。 - supercat

2

一种技术是先比较最长的名称 - 将测试(或包含关键字的表格)排序,使较长的名称在较短之前。然而,以您的示例为例:

GetPrimaryStatus
GetPrimary

您可能希望确保GetPrimaryIgnition不被识别为GetPrimary。因此,您需要使用两个字符串中较长的长度-消息或关键字进行比较。

您的数据结构可能如下:

static const struct
{
    char   *name;
    size_t  name_len;
    int     retval;
} Messages[] =
{
    { "getPrimaryStatus", sizeof("getPrimaryStatus"), CMD_PRIMARYSTATUS },
    { "getPrimary",       sizeof("getPrimary"),       CMD_PRIMARY       },
    ...
};

您可以遍历此表以查找相关命令。通过一些小心的操作,您可以限制需要查看的范围。请注意,sizeof()值包括字符串末尾的NUL。如果您可以将消息设置为null终止,则这非常有用。
但是,如果您可以在消息中将命令词设置为null终止,无论是通过复制消息到其他位置还是直接修改消息,都会更加简单。然后,您可以使用strcmp()而不是strncmp()。最短唯一前缀查找更难编写。
使用strcspn()是找到命令字的一种可行方式 - 假设您的命令全部为字母或字母数字组合。

1

我感觉你正在使用strncmp来防止缓冲区溢出,然而,消息已经被复制到内存中(即消息缓冲区)。此外,原型

int strncmp ( const char * str1, const char * str2, size_t num );

表示该函数没有副作用(即它不会更改输入缓冲区),因此不应存在它将覆盖缓冲区并更改内存的风险。(对于strcpy()而言不是这种情况。)

您可以确保消息缓冲区的长度大于最长命令字符串,以便始终访问自己拥有的内存。

此外,如果您坚持使用strncmp,可以将命令列表存储在数组中,并按从大到小排序。 您可以将每个字符串与长度(可能还有执行处理程序的函数指针)相关联。

最后,您可以找到C版本的C++称为map,Ruby或PHP称为关联数组的东西。 这使得库可以高效正确地处理此if-else树。


0
不要使用strncmp(),改用strlcmp()。它更安全。

2
strlcmp()似乎不是任何标准的一部分;如果我在网上找到的实现是典型的话,那么它的行为会退化为strncmp()的行为,如果一个字符串是另一个字符串的子集。这正是OP不想要的。 - Dan Breslau

0
你的消息中只包含这些命令之一,还是后面跟着空格/括号等的命令字符串?
如果是前者,则放弃使用 `strncmp` ,直接使用 `strcmp` 。
如果是后者,则简单地检查 `isspace(message[strlen(command)])` 或 `message[strlen(command)]=='('` 或类似情况。(注意:`strlen(command)` 是一个常量,你应该将其写成常量或使用宏从字符串字面量的大小中获取它。)

2
使用 sizeof("KeyWord")-1 - 因为 sizeof() 也会计算终止的空字符。当然,这是一个编译时常量。 - Jonathan Leffler
是的,我想我应该提到所有这些。 - R.. GitHub STOP HELPING ICE

0

使用strcmp函数,但也要比较两个字符串的长度。如果长度相同,则strcmp将给出您所需的结果。


0

使用strncmp判断两个字符串是否相等的唯一安全方法是预先验证这些字符串具有相同的长度:

/* len is a placeholder for whatever variable or function you use to get the length */
if ((len(a) == len(b)) && (strncmp(a, b, len(a)) == 0))
{
    /* Strings are equal */
}

否则,你的比较将会匹配到比它更长或者更短的内容: strncmp(a, "test", strlen("test")) 会匹配"testing"、"test and a whole bunch of other characters"等等。 strncmp(a, "test", strlen(a)) 会匹配""、"t"、"te"、"tes"。

但问题在于我们无法保证s1是一个有效的以NUL结尾的字符串,这就是n的含义。 - Steve Lau

-1
从我一年前进行C编程的记忆中挖掘,我认为第三个参数应该告诉函数要处理多少个字符进行比较。这就是为什么它很安全,因为您可以控制要处理多少个字符。
所以应该是类似这样的东西:
if(strncmp(message, "getPrimary", strlen("getPrimary")) {
   //
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接