有没有安全的strcmp函数?

15

我写了一个类似这样的函数:

bool IsSameString(char* p1, char* p2) 
{
     return 0 == strcmp(p1, p2);
}
问题在于有时候会不小心传递非字符串参数(这意味着p1p2没有以null字符结尾)。然后,strcmp继续比较直到达到不可访问的内存并崩溃。 是否有一个安全版本的strcmp?或者我可以以安全的方式判断p1(和p2)是否为字符串?

我可能错误地重新标记了你的问题为C。你是在寻找C还是C ++的解决方案? - P Shved
请记住,即使是strncmp也不能保证安全。 - Agnel Kurian
@Pavel Shved,这里有一个bool返回类型,而C语言没有bool类型,所以我认为这是C++。 - Federico klez Culloca
8
如果这是C ++,那么安全的解决方案是使用字符串类(string class)。至于布尔类型,C99是否也引入了它。 - jalf
1
#include 我希望... - Ephemera
8个回答

21

没有(标准)方法可以判断 char * 是否实际指向有效的内存。

在您的情况下,最好使用 std::string 代替所有字符串中的 char *,以及重载 == 运算符。如果这样做,编译器会强制执行类型安全。

编辑:根据下面的评论,如果您发现自己处于这样一种情况:有时将可能是有效字符串的 char * 传递给期望以空字符结尾的字符串的函数,那么您的方法基本上是错误的,可以参考@janm的回答。


3
如果您使用std::string,就不需要编写类似于您所编写的函数,这可以节省您的时间和精力。 - JBRWilkinson
3
如果其中一个字符串是从外部输入的,那么使用std::string仍然无法解决问题。归根结底,std::string仍然必须要由char实例化出来。如果这个char没有指向以null结尾的字符串,std::string构造函数就会崩溃或者有未定义的行为。唯一真正的解决方法是对字符串进行某种方式的最大长度限制,可以在构造std::string实例时进行限制,也可以在使用null结尾的char*路线时使用strncmp而不是strcmp。 - luis.espinal
-1:对于正在使用C语言的人来说,建议使用C++“string”并不是一个真正的答案。“编译器将强制执行类型安全”……只要有人不使用旧的C样式转换(例如“std::string xxx =(char *)whatever;”)。你可能会建议使用比“char *”更复杂的东西来表示字符串。例如,“struct mystr { uint32_t magicnum; ...}”,其中“magicnum”必须具有特定的值;在这里使用“std:string”没有帮助。 - Ingo Blackman
1
@IngoBlackman 这个问题标记为C++。此外,该函数返回bool,因此假设是C++也是合理的。此外,这个答案已经超过3年了。 - Wernsey
@IngoBlackman 我的意思不是 std::string xxx=(char *)whatever; - OP 应该使用 std::string 来处理 所有 字符串。这样他就可以利用编译器提供的功能,比如类型安全和重载的 == 运算符(这将消除他对 安全 strcmp 函数的需求)。但你说得对,如果 OP 的 char* 有时指向字符串,有时指向无效数据,那么在更基本的层面上就有问题了。 - Wernsey
显示剩余2条评论

18

在某些情况下,std::strncmp 可以解决你的问题:

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

该函数将C字符串str1的前num个字符与C字符串str2进行比较。


此外,看一下美国国土安全部国家网络安全局在这个问题上的建议

确保在传递给strcmp函数之前,字符串已经以null结尾。可以通过在缓冲区的最后一个分配的字节中始终放置\0来强制执行此操作。

char str1[] ="something";
char str2[] = "another thing";
/* In this case we know strings are null terminated. Pretend we don't. */
str1[sizeof(str1)-1] = '\0';
str2[sizeof(str2)-1] = '\0';
/* Now the following is safe. */
if (strcmp(str1, str2)) { /* do something */ } else { /* do something else */ }

如果操作系统是Linux,了解strncmp包含在LSB中可能会很有用:http://dev.linuxfoundation.org/navigator/browse/int_single.php?cmd=list-by-name&Ilibrary=libc&Iname=strncmp - P Shved
如果其中一个字符串比num短,仍然不安全。 - Anton Kukoba

14

如果你将非空结尾的字符串传递给strcmp()函数,那么你已经失败了。事实上,你有一个不应该缺少空结尾的字符串(但实际上确实缺少了),这表明你的代码存在更深层次的问题。你不能更改strcmp()函数以安全地解决这个问题。

你应该编写代码,使其不可能发生这种情况。首先使用string类。在你将数据引入代码的边界处,你需要确保处理异常情况;如果你获取太多的数据,你需要采取正确的措施。这不涉及运行超出缓冲区的末尾。如果必须将数据输入到C样式的缓冲区中,请使用指定缓冲区长度并检测和处理缓冲区在某一点上不够大的情况的函数。


3
是的,janm所说的没错。当你发现一个bug时,应该修复它,而不是试图通过让其他代码变得更加复杂来掩盖它,以期减轻bug的症状。在这种情况下,调用IsSameString()的代码中存在一个bug。 - Jeremy Friesner

7

没有可以移植的解决方法。约定规定,字符串本身属于正确分配的内存块,并且有一个额外的字符包含空字符。如果遵循这个约定,一切都好,否则会发生未定义的行为。

如果您知道要比较的字符串的长度,可以使用strncmp(),但如果传递给您的代码的字符串实际上比您要比较的字符串短,则无济于事。


3

您可以使用strncmp,但如果可能的话,请使用std :: string来避免许多问题 :)


如果使用未以 null 结尾的 char* 构造 std::string 实例,则仍可能存在漏洞。为了编写真正安全的代码,必须根据上下文(无论是在使用 strncmp 时还是在实例化 std::string 时)强制执行最大长度。 - luis.espinal

1

您可以使用strncmp函数来限制要比较的字符数量上限。


1
但这没有意义。一个正常的 strcmp(s1, s2) 不会在 s1 中查找超过 s2 字符数的内容,同时,你必须比较它们中最短的字符串的完整长度.. 所以 strncmp 无法帮助解决问题。 - u0b34a0f6ae

-1

关于这个问题并没有最好的答案,因为你不能验证 char* 是否是字符串。唯一的解决方案是创建一个类型并将其用于字符串,例如 str::string,或者自己创建一个轻量级的字符串类型。

struct MyString
  {
  MyString() : str(0), len(0) {}
  MyString( char* x ) { len = strlen(x); str = strdup(x); }
  ⁓MyString() { if(str) free(str); }
  char* str;
  size_t len;
  };

bool IsSameString(MyString& p1, MyString& p2) 
  {
  return 0 == strcmp(p1.str, p2.str);
  }


MyString str1("test");
MyString str2("test");

if( IsSameString( str1, str2 ) {}

1
这只是改变了失败的点;如果在构造函数中传递的内存没有以 null 结尾,仍然会发生失败。 - janm
“想要更轻量级”似乎是失败的原因。std::string可以做到你的类所能做的一切,而且没有额外的成本。轻量级并不等于功能较少。 - GManNickG
我认为这是一个问题,你只能使用允许静态字符串(如"")并使所有其他char*的使用在编译时出错,才能解决。 - David Allan Finch
GMan - 我不确定,但我预计std::string会大幅增加你的执行文件的大小。这完全取决于您对“更轻”等术语的理解。 - David Allan Finch
janm和gman - 我没有写一个MyString类的意图,只是为了证明我知道上面那个有什么错误。例如,strcmp应该使用len。至于MyString s(broken_pointer)。这可能应该是一个const char*,具有显式的ie只允许"",但这并不能阻止某人放入一个损坏的字符串。你无法在C或C++中修复这个问题。零终止的str是一种习惯用法,你要么遵循这个习惯用法,要么打破它并得到你应得的结果。 - David Allan Finch
显示剩余4条评论

-2

6
IsBadXXXPtr函数通常不是一个好的选择——因为指针指向哪里不确定,它们可能会在程序的其他部分导致随机崩溃,而要调试距离崩溃原因只有半小时和十六个源文件之遥的崩溃会更加困难。详细信息请参见http://blogs.msdn.com/oldnewthing/archive/2006/09/27/773741.aspx。 - Stephen Veiss
不仅如此,它还掩盖了其他问题。从操作系统的角度来看,指针可能是有效的,但内存可能是其他东西(比如堆栈上的地址...)。 - janm
1
哦,谢谢你的这些评论。我也有类似的经历,但是把它归咎于自己的问题。 - RED SOFT ADAIR

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