如何使用 strncpy_s() 函数实现 strncpy() 功能?

Posted

技术标签:

【中文标题】如何使用 strncpy_s() 函数实现 strncpy() 功能?【英文标题】:How to achieve strncpy() functionality with strncpy_s() function? 【发布时间】:2011-02-18 07:18:21 【问题描述】:

在某些情况下,我确实需要strncpy() 功能 - 例如,我在预定义接口中有一个函数,该函数传递缓冲区地址和缓冲区大小:

HRESULT someFunction( char* buffer, size_t length );

据记录,我可以在那里复制一个长度不超过 length 的空终止字符串 - 如果它的长度正好是 length 我不会空终止字符串并且调用者知道字符串以空字符或长度 length 结尾,以先发生者为准。

当然我会使用strncpy()

HRESULT someFunction( char* buffer, size_t length )

    const char* toCopy = ...
    size_t actualLength = strlen( toCopy );
    if( actualLength > length ) 
        return E_UNEXPECTED; // doesn't fit, can't do anything reasonable 
    
    strncpy( buffer, toCopy, length );
    return S_OK;

现在我有了这段代码,需要将它从 Visual C++ 7 迁移到 Visual C++ 9。我编译它并使用 see a warning that strncpy() is unsafe,我应该改用 strncpy_s()。

strncpy_s() 旨在始终以空值终止缓冲区,因此在上述情况下我不能将其用作直接替换。我必须在比length - 1 长的字符串上返回E_UNEXPECTED(不是以前的length),否则一旦字符串为length 或更长,它就会触发无效参数错误处理程序,否则程序将运行到未定义的行为。

到目前为止我应用的解决方案是定义一个_CRT_SECURE_NO_WARNINGS 并让编译器关闭。

有没有办法使用strncpy_s() 作为strncpy() 的实际替代品?

【问题讨论】:

【参考方案1】:

您在这里面临的问题是您的函数本身是不安全的,就像strncpy() 一样。这是不安全的,因为您的函数的调用者可能会忘记返回的字符串不是以空值结尾的。如果这确实是您的函数所需的行为,我建议不要定义 _CRT_SECURE_NO_WARNINGS 并全局禁用警告,而是改用 #pragmas

// document here exactly why you can not use strncpy_s
#pragma warning( push )
#pragma warning( disable : 4996 )
// your code that uses strncpy instead of strncpy_s
#pragma warning( pop ) 

这样,您只能在绝对必须使用不安全函数的情况下禁用这些警告。

【讨论】:

【参考方案2】:

您可以改用memcpy_s。

HRESULT someFunction( char* buffer, size_t length )

    const char* toCopy = ...
    size_t actualLength = strlen( toCopy );
    if( actualLength > length ) 
        return E_UNEXPECTED; // doesn't fit, can't do anything reasonable 
    
    else if ( actualLength < length ) 
        actualLength++; // copy null terminator too
    
    memcpy_s( buffer, length, toCopy, actualLength );
    return S_OK;

【讨论】:

这不太对,因为它会在空终止符之后复制数据。正确的行为是在空终止符之前复制数据,并用零填充目标中的任何剩余空间。两个关键方面:(1)永远不会读取原始字符串末尾的数据;请注意,此类数据可能包含安全敏感信息; (2) 目标的所有元素都写入定义的值。 @supercatmemcpy_s的最后一个参数用于确定要复制多少字节,所以它永远不会复制空终止符之后的数据。 (2) 确实,在空终止符之后的目标元素没有获得默认值,但在这个问题中没有问到这一点。在大多数软件中,无论如何都没有理由这样做。但我想这是一个不同的讨论。 我看错了代码;我错了为什么它不模仿 strncpy,但是如果程序需要 strncpy 功能,那么它不填零的事实将是一个问题。零填充字符串并不是特别深奥。它们通常用于将字符串存储到固定长度的记录中。如果代码要在记录上使用 memcpy() 或 fwrite() 之类的东西,则零填充很重要。【参考方案3】:

尝试将str*cpy*() 函数用于具有固定目标缓冲区的场景是一种常见的误解。这里的交易是这些函数“复制直到出现空字符或其他条件”。在这种情况下,有以下代码:

size_t actualLength = strlen( toCopy );
if( actualLength > length ) 
    return E_UNEXPECTED; // doesn't fit, can't do anything reasonable 

因此代码在继续复制之前知道实际的字符串长度。一旦知道长度,只有使用memcpy() 才有意义,这对于此类场景来说简单明了,而且副作用也更快,因为它允许一次复制多个字符并且不检查每个字符空终止符。

HRESULT someFunction( char* buffer, size_t length )

    const char* toCopy = ...
    size_t actualLength = strlen( toCopy );
    if( actualLength > length ) 
        return E_UNEXPECTED; // doesn't fit, can't do anything reasonable 
    
    memcpy( buffer, toCopy, min( length, actualLength + 1 ) );
    return S_OK;

所以解决方案是忘记strncpy()strncpy_s() 并改用memcpy()

【讨论】:

【参考方案4】:

您希望在actualLength &lt; length 处以空值终止,而在actualLength == length 处不以空值终止,对吧?

所以对于actualLength &lt; lengthmemcpy_s 其中actualLength == length 的情况使用strncpy_s

【讨论】:

以上是关于如何使用 strncpy_s() 函数实现 strncpy() 功能?的主要内容,如果未能解决你的问题,请参考以下文章

源代码中的条件编译

求助ffmpeg.exe在windows自带的cmd下运行不了。显示无法定位程序输入点strncpy_s于动态链接库msvcrt.dll

python如何实现index()功能,不能使用index()、find()函数方法

如何实现php字符串翻转?

memcpy和strcpy的区别

对于没有参数的情况,如何实现str.split