在 C++ 中处理字符串时如何使用 memset? [关闭]

Posted

技术标签:

【中文标题】在 C++ 中处理字符串时如何使用 memset? [关闭]【英文标题】:How to use memset while handling strings in C++? [closed] 【发布时间】:2019-10-21 03:10:47 【问题描述】:

我来自 Python 背景,最近学习 C++。我正在学习一个名为 memset 的 C/C++ 函数,并按照网站 https://www.geeksforgeeks.org/memset-in-cpp/ 的在线示例进行操作,其中出现了一些编译错误:

/**
 * @author      : Bhishan Poudel
 * @file        : a02_memset_geeks.cpp
 * @created     : Wednesday Jun 05, 2019 11:07:03 EDT
 * 
 * Ref: 
 */

#include <iostream>
#include <vector>
#include <cstring>

using namespace std;

int main(int argc, char *argv[])
    char str[] = "geeksforgeeks";

    //memset(str, "t", sizeof(str));
    memset(str, 't', sizeof(str));

    cout << str << endl;

    return 0;

使用单引号 't' 时出错 这会打印额外的字符。

tttttttttttttt!R@`

使用带双引号的“t”时出错

$ g++ -std=c++11 a02_memset_geeks.cpp 
a02_memset_geeks.cpp:17:5: error: no matching function for call to 'memset'
    memset(str, "t", sizeof(str));
    ^~~~~~
/usr/include/string.h:74:7: note: candidate function not viable: no known
      conversion from 'const char [2]' to 'int' for 2nd argument
void    *memset(void *, int, size_t);
         ^
1 error generated.

如何在 C++ 中使用 memset?

进一步研究 这里给出了具有memset 缺点的优秀教程: https://web.archive.org/web/20170702122030/https:/augias.org/paercebal/tech_doc/doc.en/cp.memset_is_evil.html

【问题讨论】:

"t"'t' 不一样。 大多数 C++ 在线学习资源都是废话,该网站也不例外,请尝试一下:***.com/questions/388242/… 为什么还要在 C++ 中使用memset?旧的 C 函数存在的原因是为了向后兼容。 这是一把上膛的枪,你把它对准你的左脚并扣动了扳机。你必须瞄准正确。 您不应该在回答问题的人下方更改问题。如果您接受评论或回答但仍然无法正常工作,您可以提出另一个问题,但这种将一个问题替换为另一个问题的编辑具有破坏性 【参考方案1】:

使用单引号 't' 时出错 这会打印额外的字符。

那是因为你覆盖了空终止符。

虽然it's not part of the logical string size,终结符是数组大小的一部分(数组不是魔法)。

所以,我想你的意思是:

memset(str, 't', strlen(str));
//               ^^^^^^

使用带双引号的“t”时出错

完全不同的东西。您告诉计算机将字符串中的每个字符设置为字符串。没有意义;不会编译。


如何在 C++ 中使用 memset?

不要。

使用类型安全的std::fill,结合std::beginstd::end

std::fill(std::begin(str), std::end(str)-1, 't');

(如果您担心性能,请不要担心:这只会通过模板专业化委托给memset,不需要优化,不会牺牲类型安全;example here in libstdc++。)

或者只是一个std::string 开始。 ?


我正在从https://www.geeksforgeeks.org/memset-in-cpp/ 学习 C++ 中的函数 memset,示例如下所示

不要尝试从随机网站学习 C++。换成a good book。

【讨论】:

不幸的是,在原始示例中它确实是sizeof。可惜这样的代码是用来“教”c++的:( @astro123 另一个选择一本好书的理由。 C++中有不同种类的文字,这与Python完全不同。 这个网站 (geeksforgeeks) 应该永远被禁止。 @astro123:从 geeksforgeeks.org/memset-in-cpp 在线学习这是你的第一个问题。该教程的小示例中有一个严重的错误。这在 geeksforgeeks.org 上并不罕见。有一些好东西,但它经常与坏东西混在一起,并且直到你已经是专家你不会知道如何区分。与 Stack Overflow 不同,geeksforgeeks 没有供人们审查帖子并表明其质量的投票机制,因此您无法知道应该信任哪些帖子。 @PeterCordes 真可惜,所以文档按照它的方式进行了......显然需要经过投票的策划教程。我相信最终会有人想出正确的设计。【参考方案2】:

此声明

char str[] = "geeksforgeeks";

声明一个字符数组,其中包含一个字符串,该字符串是一个字符序列,包括终止零符号'\0'

你可以想象声明如下等价方式

char str[] = 
 
    'g', 'e', 'e', 'k', 's', 'f', 'o', 'r', 'g', 'e', 'e', 'k', 's', '\0'
;

函数memset的本次调用

memset(str, 't', sizeof(str));

覆盖数组的所有字符,包括终止零。

所以下一条语句

cout << str << endl;

导致未定义的行为,因为它在遇到终止零之前输出字符。

你可以改写

#include <iostream>
#include <cstring>

int main()

    char str[] = "geeksforgeeks";

    std::memset( str, 't', sizeof( str ) - 1 );
    
    std::cout << str << '\n';

或者如下方式

#include <iostream>
#include <cstring>

int main()

    char str[] = "geeksforgeeks";

    std::memset( str, 't', std::strlen( str ) );
    
    std::cout << str << '\n';

这就是保持数组中的终止零不变。

如果你想覆盖数组的所有字符,包括终止零,那么你应该替换这个语句

std::cout << str << '\n';

对于这个声明

std::cout.write( str, sizeof( str ) ) << '\n';

如下面的程序所示,因为数组现在不包含字符串了。

#include <iostream>
#include <cstring>

int main()

    char str[] = "geeksforgeeks";

    std::memset( str, 't', sizeof( str ) );
    
    std::cout.write( str, sizeof( str ) ) << '\n';

至于这个电话

memset(str, "t", sizeof(str));

那么第二个参数的类型(即类型const char *)不对应于类型为int 的第二个函数参数的类型。见函数声明

void * memset ( void * ptr, int value, size_t num );

因此编译器会发出错误消息。

除了字符数组(即使在 C++ 中也经常使用),您还可以使用模拟字符串的标准类 std::string(或 std::basic_string)。

在这种情况下,不需要使用标准 C 函数 memset 来用单个字符填充字符串。最简单的方法如下

#include <iostream>
#include <string>

int main()

    std::string s( "geeksforgeeks" );
    
    s.assign( s.length(), 't' );
    
    std::cout << s << '\n';

另一种方法是使用标头&lt;algorithm&gt; 中声明的标准算法std::fillstd::fill_n。例如

#include <iostream>
#include <string>
#include <iterator>
#include <algorithm>

int main()

    std::string s( "geeksforgeeks" );
    
    std::fill( std::begin( s ), std::end( s ), 't' );
    
    std::cout << s << '\n';

#include <iostream>
#include <string>
#include <iterator>
#include <algorithm>

int main()

    std::string s( "geeksforgeeks" );
    
    std::fill_n( std::begin( s ), s.length(), 't' );
    
    std::cout << s << '\n';

你甚至可以使用 std::string 类的方法 replace 以下方法之一

#include <iostream>
#include <string>

int main()

    std::string s( "geeksforgeeks" );
    
    s.replace( 0, s.length(), s.length(), 't' );
    
    std::cout << s << '\n';

或者

#include <iostream>
#include <string>

int main()

    std::string s( "geeksforgeeks" );
    
    s.replace( std::begin( s ), std::end( s ), s.length(), 't' );
    
    std::cout << s << '\n';

【讨论】:

原帖清楚地表明用户正在尝试学习C++。请至少提一下,如果您使用std::string,这些都无关紧要,应该在这里使用它而不是使用这种复杂的C 东西。 (虽然不是在课程开始时知道,但可能相关) @JVApen 原帖清楚地表明用户正在尝试了解如何将 memset 与字符数组一起使用。:) 好答案。如果您希望它对 OP 更好:请注意类型系统的差异。 C++ 有一个静态类型系统,其中变量具有固定的静态类型。 Python 有一个完全动态的类型系统,其中值有类型而变量没有。这可能是他混淆't'"t"的原因。 模拟字符串”是什么意思? @Ray 你错了。对于初学者来说,正确的声明看起来像 const char *str = "geeksforgeeks";在任何情况下,C 和 C++ 中的字符串文字都是不可变的。任何更改字符串文字的尝试都会导致未定义的行为。【参考方案3】:

这是 memset 的正确语法...

void* memset( void* dest, int ch, std::size_t count );

将值 ch 转换为 unsigned char 并将其复制到 dest 指向的对象的第一个 count 个字符中。如果对象是潜在重叠的子对象或者不是 TriviallyCopyable(例如,标量、C 兼容的结构或普通可复制类型的数组),则行为未定义。如果 count 大于 dest 指向的对象的大小,则行为未定义。

(source)

对于第一个语法memset(str, 't', sizeof(str));。编译器因为额外的大小而抱怨。它打印了 18 次 tttttttttttttt!R@。我建议尝试使用 sizeof(str) -1 来处理 char 数组。

对于第二个语法memset(str, "t", sizeof(str));,您提供的第二个参数是一个字符串。这就是编译器报错的原因:从‘const char*’到‘int’的无效转换

【讨论】:

可能重叠的子对象是什么? UB 不会自动修改 C++ 中其他对象的对象表示。例如,uint32_t 具有完全定义的对象表示(除了字节序字节序)。所以不清楚你在说什么样的重叠,因为memset只需要一个指针arg;其他参数是按值。与memmove 不同,该措辞对于禁止重叠的memcpy 是有意义的。 @PeterCordes 公平地说,这句话是从 cppreference.com 抄袭的。所以如果有错,cppreference.com 需要更正。 @LightnessRacesinOrbit:在 cppref 上,该短语是指向 a definition 的超链接,这是有道理的。如果 memset 也可能正在修改另一个对象的字节,那么它是 UB 是有可能的(因为指针指向使用 [[no_unique_address]] 声明的结构的子对象,允许编译器做它想做的任何事情,包括创建位域我猜对于窄或布尔类型)。我对“基类子对象”部分不太清楚;可能是 UB,因为它可以覆盖 vtable 指针? @PeterCordes - 它在谈论something like this。在这里,base 可以简单地复制,但它对于memset(或memmove)不安全,或者因为它是一个可能重叠的子对象。注意sizeof(base) == 8,但是当它被用作derived(它本身有一个char成员)的基础时,sizeof(derived) == 8!所以派生的成员存储在base的填充中。因此,用memset 覆盖任意base&amp; 是不安全的,因为在这种情况下,您还会破坏派生成员。 还要注意这如何反映在 gcc 上的代码生成中,用于在 b = base 中清零 base:它执行 qwordbyte 写入,因为它不能安全地扩展它到单个qword 写入,因为填充可能被重用。然后查看base2derived2:除了base2struct 而不是class,它们是相同的。然后它变成一个聚合,我猜重叠是被禁止的(注意b = base2 codegen 是如何变化的)。【参考方案4】:

Vlad 对您问题的第一部分的回答很有帮助,但我觉得可以更直观地解释第二部分:

正如其他人提到的,'t' 是一个字符,而"t" 是一个字符串,字符串末尾有一个空终止符。这使得"t" 不是一个而是 两个 字符的数组 - ['t', '\0']!这使得 memset 的错误更加直观——它可以很容易地将单个 char 强制转换为 int,但是当它被赋予一个 chars 数组时它会窒息。就像在 Python 中一样,int(['t', '\0'])(或ord(['t', '\0']))不会计算。

【讨论】:

更准确地说,当传递“t”时,传递“t”中的“t”地址。因此,如果它被转换为memset 中的int 参数,它将是指向't' 的指针被转换为int,而不是字符串的值被转换为int

以上是关于在 C++ 中处理字符串时如何使用 memset? [关闭]的主要内容,如果未能解决你的问题,请参考以下文章

在 C++ 中将 memset 与多维数组一起使用

为啥或为啥不在 C++ 中使用 memset? [关闭]

C++ memset

Memset 没有填满整个指针数组 c++

如何在 C++ 字符串中填充一个部分?

在 C++ 中使用 memset 初始化具有不同值的结构数组元素