순간을 성실히, 화려함보단 꾸준함을

memcpy에 string 형을 쓰면 안될까???? 본문

알고리즘,SQL/백준,BOJ

memcpy에 string 형을 쓰면 안될까????

폭발토끼 2022. 2. 1. 16:51

안녕하세염

이번글은 memcpy 함수를 사용할때 겪었던 경험을 풀려고 합니다.

https://www.acmicpc.net/problem/3085

이 문제를 풀때 배열을 복사하기 위해서 평소 사용도 안해본 memcpy 함수를 사용해봤습니다.

#include<bits/stdc++.h>

using namespace std;
using ll = long long;
int n;
string board[50];
string cp_board[50];
void solve();
int go();

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t = 1;
    while (t--)solve();
    return 0;
}
void solve()
{

    int dx[] = { -1,1,0,0 }, dy[] = { 0,0,-1,1 };
    cin >> n;
    for (int i = 0; i < n; i++)cin >> board[i];

    int ans = 1;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            for (int d = 0; d < 4; d++) {
                int tx = i + dx[d];
                int ty = j + dy[d];

                if (tx < 0 || tx >= n || ty < 0 || ty >= n)continue;

                swap(board[i][j], board[tx][ty]);
                ans = max(ans,go());
                swap(board[i][j], board[tx][ty]);
            }
        }
    }
    cout << ans;
}
int go()
{
    int ret = 0;

    memcpy(cp_board, board, sizeof(board));
    /*for (int i = 0; i < n; i++)
        cp_board[i] = board[i];*/
    for (int i = 0; i < n; i++) {
        int cnt = 0;
        //sort(cp_board[i].begin(), cp_board[i].end());

        for (int j = 1; j < n; j++) {
            if (cp_board[i][j - 1] == cp_board[i][j])
                cnt++;
            else {
                ret = max(ret, cnt);
                cnt = 0;
            }
            ret = max(ret, cnt);
        }
    }
    /*for (int i = 0; i < n; i++)
        cp_board[i] = board[i];*/
    memcpy(cp_board, board, sizeof(board));
    for (int i = 0; i < n; i++) {
        int cnt = 0;
        string str;
        for (int j = 0; j < n; j++)
            str.push_back(cp_board[j][i]);
        //sort(str.begin(), str.end());
        for (int j = 1; j < n; j++) {
            if (str[j - 1] == str[j])
                cnt++;
            else{
                ret = max(ret, cnt);
                cnt = 0;
            }            
            ret = max(ret, cnt);
        }
    }
    return ret+1;
}

근데 로컬에서 출력할때 이상하다싶더니 제출하니 '메모리초과'가 발생하게 되버렸습니다.

그래서 공식 레퍼런스 문서를 찾다보니,

If the objects are potentially-overlapping or not TriviallyCopyable, the behavior of memcpy is not specified and may be undefined.
(potentially-overlapping 이거나 TriviallyCopyable이 아닌 경우에 memcpy 함수의 동작이 정의되지 않을 수 있습니다.)
라는 뜻인데, 여기서 대체 potentially-overlappingTriviallyCopyable 이 뭘까요??

potentially-overlapping 단어를 선택해서 링크를 타고 들어가면 이런 글이 있습니다.

A subobject is potentially overlapping if it is a base class subobject or a non-static data member declared with the attribute
(하위 객체가 기본 클래스 하위 객체이거나 속성으로 선언된 비정적 데이터 멤버인 경우 잠재적으로 겹칠 수 있습니다.)

즉, 복사되는 메모리와 복사할 메모리가 겹칠 수도 있는 현상을 뜻합니다. memcpy 함수를 사용해서 복사를 할텐데 메모리 공간이 겹치면 안됨을 의미합니다.

사실 이 문제를 틀렸던 이유 중 하나는 바로 TriviallyCopyable 때문이었습니다.
역시 단어를 선택하고 링크를 타고 들어가면 친절하게 정의가 되어있습니다.
[https://en.cppreference.com/w/cpp/named_req/TriviallyCopyable]

핵심은 TriviallyCopyable 가 될 조건이 여러가지가 있는데 string형이 이 조건에 부합하지 않기 때문에 결국 TriviallyCopyable 이 될 수 없다라는 것 입니다.

그렇기 때문에 memcpy 에는 string 형을 사용할 수가 없습니다.

#include <bits/stdc++.h>
using namespace std;

int main()
{
    if (is_trivial<string>())
        cout << "Hello World!\n";
}

이 소스를 실행시키면 아무것도 출력이 되지 않음을 확인하실 수 있으실 겁니다.

결론 : memcpy를 사용할때 절대 string형은 사용하지 말자