二维数组中的 Memset

Posted

技术标签:

【中文标题】二维数组中的 Memset【英文标题】:Memset in 2D arrays 【发布时间】:2019-05-06 14:54:51 【问题描述】:

我对静态二维数组有疑问。我想在第 k + 1 行之后重置元素,我想使用 memset。

我写了这段代码,但它并没有重置第 k + 1 行之后的所有行:

int a[505][505];
..................
for(int i=1;i<=n;i++)
     for(int j=1;j<=m;j++)
         f >> a[i][j];
memset(a + k + 1 , 0 , sizeof(int) * (m + 1) * (n - k));

此代码不会重置第 k + 1 行之后的所有行。

编辑:n = 二维数组有多少行 m = 二维数组有多少列

编辑:我有一个更大的问题,每次我在我的问题中做某事时,我都需要重置第 k + 1 行。

【问题讨论】:

c++ 中的索引是从 0 开始的。所以a[0]a[504] 是有效的。如果nm505(这是我的猜测),那么您将拥有超出范围的索引。 我不知道。 m 是什么? n 是什么?请分享minimal reproducible example。此示例不够完整,无法回答您的问题。 那么由于我在第一条评论中描述的问题,您有未定义的行为。 您可以像这样进行零初始化,而不是 memsetint a[505][505]; 另外请阅读how to ask good questions,以及this question checklist。好问题导致好答案,坏问题导致坏答案(以及大量的猜测和猜测)。 【参考方案1】:

数组的索引从0 开始。因此,如果您有一个 N 元素数组,则索引的有效范围是 [0, N)

这是一个演示程序,展示了如何将函数 memset 与整数数组一起使用。

#include <iostream>
#include <iomanip>
#include <cstring>

int main()

    const size_t N = 5;
    const size_t M = 10;
    int a[N][M];

    size_t k = 2;

    for ( size_t i = 0; i < k; i++ )
    
        for ( size_t j = 0; j < M; j++ ) a[i][j] = M * i + j;
    

    std::memset( a[k], 0, ( N - k ) * M * sizeof( int ) );

    for ( const auto &row : a )
    
        for ( const auto &value : row ) std::cout << std::setw( 2 ) << value << ' ';
        std::cout << '\n';
            

它的输出是

 0  1  2  3  4  5  6  7  8  9 
10 11 12 13 14 15 16 17 18 19 
 0  0  0  0  0  0  0  0  0  0 
 0  0  0  0  0  0  0  0  0  0 
 0  0  0  0  0  0  0  0  0  0 

如果您只想将一行(例如第 k 行)重置为零,那么 memset 的调用将如下所示

std::memset( a[k], 0, M * sizeof( int ) );

更通用的方法是使用标准算法std::fill。例如

std::fill( std::begin( a[k] ), std::end( a[k] ), 0 );

例如

#include <iostream>
#include <iomanip>
#include <iterator>
#include <algorithm>

int main()

    const size_t N = 5;
    const size_t M = 10;
    int a[N][M];

    size_t k = 2;

    for ( size_t i = 0; i < N; i++ )
    
        for ( size_t j = 0; j < M; j++ ) a[i][j] = M * i + j;
    

    std::fill( std::begin( a[k] ), std::end( a[k] ), 0 );

    for ( const auto &row : a )
    
        for ( const auto &value : row ) std::cout << std::setw( 2 ) << value << ' ';
        std::cout << '\n';
            


程序首先顺序填充数组的所有元素,然后将第 k 行重置为零。

当然,最初用零声明数组初始化它而不调用函数memset 会更简单(编译器会自己做)。

int a[N][M] = ;

【讨论】:

在我的情况下只重置第 k 行 @Sochuu 你用的是什么编译器? @Sochuu 这不重要。我提出了一个通用的方法。 @Sochuu 这是 OP 发布的 memset std::memset( a[k], 0, ( N - k ) * M * sizeof( int ) );。高度怀疑启用优化后填充会变慢。 std::fill 在几乎所有编译器和标准库实现中都被编译为对memset 的调用,因此您声称std::fill 太慢了,因此您想使用memset 来代替逻辑,@Sochuu。非凡的主张需要非凡的证据。使用提供这些性能声明的测试用例更新您的问题,有人会很乐意进行调查。【参考方案2】:

我们这里不做 C ......请使用比 M 和 N 更多字母的标识符。

#include <cstddef>
#include <iostream>
#include <algorithm>
#include <numeric>
#include <iterator>

template<typename T, std::size_t ROWS, std::size_t COLS>
void print_arr(T (&arr)[COLS][ROWS])

    for (size_t row; row < ROWS; ++row) 
        std::copy(&arr[row][0], &arr[row][0] + COLS,
                  std::ostream_iterator<T> std::cout, "\t" );
        std::cout.put('\n');
    
    std::cout.put('\n');


template<typename T, std::size_t ROWS, std::size_t COLS>
void kill_all_from_line_till_last(T (&arr)[COLS][ROWS], std::size_t kill_from)

    std::fill(&arr[kill_from][0], &arr[kill_from][0] + (ROWS - kill_from) * COLS, T);


int main()

    constexpr size_t rows     10 ;
    constexpr size_t columns  10 ;

    int arr[rows][columns];
    std::iota(&arr[0][0], &arr[0][0] + columns * rows, 1);
    print_arr(arr);

    kill_all_from_line_till_last(arr, 7);

    print_arr(arr);


输出:

1       2       3       4       5       6       7       8       9       10
11      12      13      14      15      16      17      18      19      20
21      22      23      24      25      26      27      28      29      30
31      32      33      34      35      36      37      38      39      40
41      42      43      44      45      46      47      48      49      50
51      52      53      54      55      56      57      58      59      60
61      62      63      64      65      66      67      68      69      70
71      72      73      74      75      76      77      78      79      80
81      82      83      84      85      86      87      88      89      90
91      92      93      94      95      96      97      98      99      100

1       2       3       4       5       6       7       8       9       10
11      12      13      14      15      16      17      18      19      20
21      22      23      24      25      26      27      28      29      30
31      32      33      34      35      36      37      38      39      40
41      42      43      44      45      46      47      48      49      50
51      52      53      54      55      56      57      58      59      60
61      62      63      64      65      66      67      68      69      70
0       0       0       0       0       0       0       0       0       0
0       0       0       0       0       0       0       0       0       0
0       0       0       0       0       0       0       0       0       0

伪 C 版本:

...使用std::memset() 看起来几乎一样:

#include <cstring>

template<typename T, std::size_t ROWS, std::size_t COLS>
void kill_all_from_line_till_last(T (&arr)[COLS][ROWS], std::size_t kill_from)

    std::memset(&arr[kill_from][0], 0, (ROWS - kill_from) * COLS * sizeof(T));

但您只能将其用于 POD。


速度:

既然您提到您发现与std::memset() 相比,std::fill() 对于您的需求来说太慢了

@搜楚:

使用填充,可以,但是太慢了。我想要memset

constexpr size_t rows     10 ;
constexpr size_t columns  10 ;

    int arr[rows][columns];
    std::iota(&arr[0][0], &arr[0][0] + columns * rows, 1);
    print_arr(arr);
    kill_all_from_line_till_last_fill(arr, 7);
    print_arr(arr);


    int arr[rows][columns];
    std::iota(&arr[0][0], &arr[0][0] + columns * rows, 1);
    print_arr(arr);
    kill_all_from_line_till_last_memset(arr, 7);
    print_arr(arr);


gcc

从 gcc 9.1 (--std=c++14 -O3 -Wall) 组装:

    ; ...

    call    void print_arr<int, 10ul, 10ul>(int (&) [10ul][10ul])
    xor     eax, eax
    mov     ecx, 15
    mov     rdi, rbx
    rep stosq
    mov     rdi, rsp
    call    void print_arr<int, 10ul, 10ul>(int (&) [10ul][10ul])

    ; ...

    call    void print_arr<int, 10ul, 10ul>(int (&) [10ul][10ul])
    xor     eax, eax
    mov     rdi, rbx
    mov     ecx, 15
    rep stosq
    mov     rdi, rsp
    call    void print_arr<int, 10ul, 10ul>(int (&) [10ul][10ul])

    ; ...

正如您所见,这两个版本在调用print_arr() 之间生成完全相同的代码。编译器并不(那么)愚蠢。

完整代码:godbolt Compiler Explorer


叮当

clang 8.3.0 (--std=c++14 -Ofast3 -Wall) 相同,std::fill()std::memset() 的代码完全相同:

    ; ...

    mov     rdi, rbx
    call    void print_arr<int, 10ul, 10ul>(int (&) [10ul][10ul])
    xorps   xmm0, xmm0
    movups  xmmword ptr [rsp + 376], xmm0
    movups  xmmword ptr [rsp + 360], xmm0
    movups  xmmword ptr [rsp + 344], xmm0
    movups  xmmword ptr [rsp + 328], xmm0
    movups  xmmword ptr [rsp + 312], xmm0
    movups  xmmword ptr [rsp + 296], xmm0
    movups  xmmword ptr [rsp + 280], xmm0
    mov     qword ptr [rsp + 392], 0
    mov     rdi, rbx
    call    void print_arr<int, 10ul, 10ul>(int (&) [10ul][10ul])

    ; ...

    call    void print_arr<int, 10ul, 10ul>(int (&) [10ul][10ul])
    xorps   xmm0, xmm0
    movups  xmmword ptr [rsp + 376], xmm0
    movups  xmmword ptr [rsp + 360], xmm0
    movups  xmmword ptr [rsp + 344], xmm0
    movups  xmmword ptr [rsp + 328], xmm0
    movups  xmmword ptr [rsp + 312], xmm0
    movups  xmmword ptr [rsp + 296], xmm0
    movups  xmmword ptr [rsp + 280], xmm0
    mov     qword ptr [rsp + 392], 0
    mov     rdi, rbx
    call    void print_arr<int, 10ul, 10ul>(int (&) [10ul][10ul])
    
    ; ...

完整代码:godbolt Compiler Explorer


msvc

微软 cl 19.20 (/O2):

    ; ...

    call    void print_arr<int,10,10>(int (&)[10][10])
    xorps   xmm0, xmm0
    lea     rcx, QWORD PTR arr$2[rsp]
    xor     eax, eax
    movups  XMMWORD PTR arr$2[rsp+280], xmm0
    mov     QWORD PTR arr$2[rsp+392], rax
    movups  XMMWORD PTR arr$2[rsp+296], xmm0
    movups  XMMWORD PTR arr$2[rsp+312], xmm0
    movups  XMMWORD PTR arr$2[rsp+328], xmm0
    movups  XMMWORD PTR arr$2[rsp+344], xmm0
    movups  XMMWORD PTR arr$2[rsp+360], xmm0
    movups  XMMWORD PTR arr$2[rsp+376], xmm0
    call    void print_arr<int,10,10>(int (&)[10][10])     ; 

    ; ...

    call    void print_arr<int,10,10>(int (&)[10][10])
    xorps   xmm0, xmm0
    lea     rcx, QWORD PTR arr$1[rsp]
    xor     eax, eax
    movups  XMMWORD PTR arr$1[rsp+280], xmm0
    mov     QWORD PTR arr$1[rsp+392], rax
    movups  XMMWORD PTR arr$1[rsp+296], xmm0
    movups  XMMWORD PTR arr$1[rsp+312], xmm0
    movups  XMMWORD PTR arr$1[rsp+328], xmm0
    movups  XMMWORD PTR arr$1[rsp+344], xmm0
    movups  XMMWORD PTR arr$1[rsp+360], xmm0
    movups  XMMWORD PTR arr$1[rsp+376], xmm0
    call    void print_arr<int,10,10>(int (&)[10][10])

    ; ...

完整代码:godbolt Compiler Explorer

我认为实验可以到此结束。

【讨论】:

以上是关于二维数组中的 Memset的主要内容,如果未能解决你的问题,请参考以下文章

对二维数组的“memset”的混淆和“free”的错误

在c++中怎么用memset() 给二维char型数组赋值

将 memset 用于特定行的二维数组

二维数组的动态分配(new)初始化(memset)和撤销(delete)

使用 memset 初始化指针元素的二维数组

如何将二维数组的所有元素初始化为java中的任何特定值