memcpy 를 다차원배열에 적용해도 되나요?
글쓴이: winner / 작성시간: 일, 2006/05/07 - 2:47오후
제가 알기로는 배열복사를 위해서 memcpy 가 있는 것으로 아는데 이상하게 불안하다는 생각이 듭니다.
공부하면 할수록 알 수 없는 것이 표준이라는 놈이라...
하여간 1차원배열은 맞겠지하면서 memcpy 를 쓰느데
2차원배열은 해도 되는지 모르겠네요.
void * 가 말그대로 일반 pointer 라면 2차원 배열을 가리켜도 문제없을 거라는 생각은 드는데
또 복사라는 문제는 어디서 문제가 숨어있는지 알 수 없으니...
padding bit 나 memory alignment restriction 같은 문제가 있는 것은 아닌지 그런 걱정이 듭니다.
2차원이 문제없으면 3차원도 역시 문제없을 것 같긴 하네요.
대충 이런 code 가 문제 없나요?
int arr[3][3];
int ar1[3][3] = {
{1, 1, 1},
{1, 1, 1},
{1, 1, 1}
};
memcpy(arr, ar1, sizeof (int) * 3 * 3);
일단 Visual C++ 2005 에서는 문제없이 실행되네요.
혹시 더 나은 방법은 없는지도 궁금하네요. memcpy 의 세번째 인자는 차라리
sizeof (int[3][3]) 이 나을까요?
C++ STL 의 copy 같은 algorithm 들은 2차원배열에는 못쓸 것 같군요.
Forums:
.
C이냐 C++이냐에 따라 틀립니다.
C++이라면 alloc 계열의 함수나 memcpy 같은 함수들은 없다고 생각하십시오. 다소 비효율적으로 보일지는 몰라도 for문으로 일일이 =연산자를 이용하여 대입하여 복사하는 것이 정답입니다. 물론 위의 코드 자체는 아무 문제가 없습니다만, int형이 아니라 다른 데이터형일때는 문제가 생길 수 있습니다.
C라면 아무 문제 없습니다. 배열의 각 원소 사이에는 빈공간이 들어갈 수 없습니다. 단, 이것이 원소 내부에 padding이 들어갈 수 없다는 얘기는 아닙니다. (원소 내부에는 들어가던 말던 상관없지만, 배열에서는 원소 사이사이에 빈공간이 들어갈 수 없다는 얘기입니다)
원본과 대상체의 위치와 크기가 확실하고, 복사할 공간의 크기만 정확하게 적어 준다면 위에 적어주신 코드는 아무 문제 없습니다.
> sizeof (int[3][3]) 이 나을까요?
어차피 컴파일 과정에서 상수 처리 되므로 둘 다 같은 결과를 낳습니다. 하지만 저는 sizeof (int) * 3 * 3 가 더 마음에 드는군요.
C++ 는 member 단위 대입으로 인하여 할 수 없는 건가요?
그렇다면 built-in type 의 경우에는 문제가 없는 것 아닌가요?
member 단위 대입은 구조체와 class 의 경우가 문제가 될텐데요.
alloc 계열을 절대 쓰지 말라는 말은 들어본 적은 있습니다만 memcpy 같은 함수도
써서는 안된다는 guide 는 처음입니다.
어쨌든 안심하고 다차원 배열에 memcpy 쓰겠네요.
위에 답변다신님
위에 답변다신님 무엇이 문제라고 하는지 잘모르겠군요...
C에는 괜찮고 C++에는 안된다? 처음듣는소린데..
그리고 저같으면 memcpy(arr, ar1, sizeof(arr));
으로합니다.
괜히 숫자적어서 나중에 배열크기라도 바뀌면 어쩌시려고..그리고 쓸데 없이 cpu도 일이늘고..
----------------------------------------------------------------------------
C++에는 C와 달리
C++에는 C와 달리 연산자 overloading이 있기 때문에 메모리를 그냥 복사하는 것 만으로는 부족한 경우가 있습니다. 그리고 sizeof(int) * 3 * 3은 컴파일러가 계산하기 때문에 실행할 때에는 CPU에 대한 부하가 없습니다.
어떤것이문제인지
어떤것이문제인지 정확히 말씀해보세요..
간단히 예제보여두 좋구요..
아무래도 컴파일러가 int 크기구해서 다시 두번의 곱을 더하려면 컴파일할때 부하거 더있겠죠..
그리고 최적화옵션을 안줘도 sizeof 외에 두번의 곱까지 컴파일단계에서 하는지는 잘모르겠군요..
----------------------------------------------------------------------------
Quote:어떤것이문제인
간단한 예라면
sizeof(arr)로 쓴다고 해도 "int 크기구해서 다시 두번의 곱"을 해야 할 것 같군요.
컴파일 단계에서 합니다. 그 덕분에 template metaprogramming도 가능하지요.
string s1, s2 는 배열이
string s1, s2 는 배열이 아니자나요.
----------------------------------------------------------------------------
Quote:하여간
memcpy()를 잘못 쓰면 패딩이나 정렬 문제가 발생할 수도 있겠으나
보다 근본적인 문제는 개체의 내부 표현을 직접 건드리는 데에 있습니다.
클래스와 같은 추상적인 자료형을 다룰 때는 절대로 써서는 안되는 것입니다.
문제는 없는 코드입니다. sizeof를 쓰시려면 차라리
memcpy(arr, ar1, sizeof(ar1));
이 낫겠습니다.
그럴 리가요. :-)
std::copy(&ar1[0][0], &ar1[3][3], &arr[0][0]);
그렇다면 다차원배열은 완전히 1차원배열과 같다는 것인가요?
memcpy(arr, ar1, sizeof(ar1)); 도 맞고
copy(&ar1[0][0], &ar1[3][3], &arr[0][0]); 도 맞다면
다차원배열에 대한 pointer 를 1차원배열의 pointer 로 형변환해도 문제없다는 것처럼 보입니다.
사실 제가 정확히 알고 싶은 또하나도 이 부분입니다.
음...
우선
int arr[3][2];
int * parr = arr;
은 불가능한 것으로 알고 있습니다.
그렇다면 직접적인 형변환은 안되지만 내부원소를 iterate 할 때
한 원소를 가리키는 pointer(1차원 배열에 대한 pointer 와 동일한 type 인)
를 사용해도 된다는 뜻인 것 같습니다.
즉 arr[3][2] 에서 arr[3][0] == *(&arr[2][1] + 1) 라는 말로 보입니다.
단지 다차원배열 구현의 내부만이 아니라 C/C++ 표준 정의 자체가 위의 code 를
합당하게 받아들인다는 말로 이해해도 될런지요.
(최근에 C 기초플러스 5판을 읽는데 위 code 를 보니 C99 의 VLA 등장배경이 떠오르네요.)
그런데
copy(&ar1[0][0], &ar1[3][3], &arr[0][0]); 이 가능하다면
대단히 멋져보이는군요.
이거라면 C++ 에서 구조체나 class 의 다차원배열도 깔끔하게 한 줄로 복사가 가능하겠네요.
전 이제까지 반복문에다 마지막 차원만 copy 를 썼거든요.
반복구조가 한단계만 줄어지는 방식으로요.
혹시 다중 vector (code 쓰기가 애매하네요. tag 를 어떻게 써야할 지 몰라서 각괄호썼다고 글 짤리더라는...)
의 깔끔한 한줄복사법도 있을런지요.
지금 보니 제 코드가
지금 보니 제 코드가 틀렸군요. 아래처럼 되어야 합니다.
std::copy(&ar1[0][0], &ar1[3][0], &arr[0][0]);
네, 그렇게 이해하셔도 좋습니다. 표준에 의하면 배열의 모든 원소는 반드시 연속으로
배치되어야 하기 때문이지요. 그런데 사실 위의 코드는 copy()를 다차원 배열에도 쓸 수
있다는 것을 보이기 위한 것일 뿐, 제 개인적으로는 별로 마음에 드는 코드는 아닙니다.
C++의 엄격함을 (약간) 무시한, 일종의 hack이라고 보기 때문입니다. 이건 저의 개인적인
스타일일 뿐이니 다르게 생각하는 분도 계시겠지요.
아마 vector< vector<int> > 같은 것을 뜻하는 것 같습니다.
그렇다면 간단히 = 연산을 쓰면 됩니다. 배열은 대입에 의해 복사가 불가능하기 때문에
memcpy()니 std::copy()니 하는 것들이 필요하지만 vector는 대입에 의한 복사가 가능하지요.
엄밀히 말해서 C에는 다차원 배열이 없습니다.
단지 1차원 배열입니다.
c++은 primitive 형의 데이타 타입이라면 문제가 없을거 같습니다.
^^ 과거 사람들이
^^
과거 사람들이 곰탕을 끌여먹으며 이것이 곰탕이다 라고 정의했는데..
후세에 어떤사람이 곰으로 탕을 만든후.. 엄밀히말해서 현제의 곰탕은 곰탕이 아니다..
이것이 곰탕이다 라고 한다면 어떨까요..
말의 의미상으로 설사 더욱 비슷하고 가깝다고 할지언정 애초 정의를
마음대로 의미를바꾸어 정의하는건 그것이 본질에서 벗어난것입니다.
즉, 설서 C++에서 C의 배열규정과 다르게 할당한다면 그것이 배열이아닌 배열변종에 불과하는거죠.
애초배열이란 이러한특징을가진다라고 못박혀있으니까말이죠..
아니면 C++의 배열이라고 하는게 맞겠죠..
----------------------------------------------------------------------------
처음 질문하신분의 글에 답하자면
memcpy(arr, ar1, sizeof (int) * 3 * 3);
이거 됩니다.
하지만
memcpy(arr, ar1, sizeof(arr));
이게 더 편합니다.
arr 의 타입이나 사이즈가 변해도
memcpy(arr, ar1, sizeof(arr));
의 코드는 수정할 필요가 없어지니깐요.
다차원배열의 내부 구현방식은 ANSI C 표준이므로 모든 컴파일러에서 잘 작동합니다.
memcpy(arr, ar1, sizeof (int) * 3 * 3);
은 arr 이 배열이 아니고 포인터일때 어쩔 수 없이 사용하는 방법이죠
만약 포인터라면
memcpy(arr, ar1, sizeof(*arr) * 3 * 3);
이런식으로 사용하시는게 더 좋습니다.
그럼 arr 포인터의 타입이 변해도 코드를 수정할 필요가 없으니깐요.
그리고 malloc/free 와 new/delete 는 C++ 에서 모두 사용해도 됩니다.
다만, malloc 으로 할당한건 free 로 해제하고 new 로 할당한건 delete 로 해제하는 규칙만 잊지 않으시면 됩니다.
또한 class 는 new/delete 로 하셔야 합니다.
class 의 생성자를 호출하고 가상함수 포인터를 셋팅하는 녀석이 new 이므로 new 로 하셔야 합니다.
정확히 말하면 생성자나 가상함수가 있으면 new로 하셔야합니다.
그래서 생성자가 있는 struct 라면 new 로 생성하셔야 합니다.
소멸자는 호출하는 녀석이 delete 이므로, 소멸자가 있으면 역시 delete 로 해제하셔야 합니다.
종합해서, 생성자나 소멸자, 가상함수가 있으면 new/delete 를 사용하세요.
에라~ 모르겠다. 싶으시면 그냥 C++에서는 new/delete 만을 사용하시면 된다는 것이지요.
C++에서 메모리 복사에 memcpy 를 사용하셔도 상관없습니다.
다만 가상함수를 포함하는 class 같은 경우는 그 가상함수 포인터까지 복사되는 side effect 가 있다는 것을 알고는 계셔야 합니다.
그런 side effect 때문에 사용하지 말라는것이지 '절대' 사용하지 말라고 하는게 아니지요.
같은 맥락으로 class 의 맴버 변수를 편리하게 memset 으로 초기화 할 수도 있습니다.
다만 가상함수가 포함되어 있다면 가상함수 포인터도 초기화 되버리는 side effect 가 있습니다.
가상함수가 없어도
가상함수가 없어도 memcpy를 쓰면 안되는 경우가 있습니다. Reference counting을 하는 클래스를 memcpy하게 되면 reference count가 바뀌지 않기 때문에 dangling pointer가 될 위험이 있습니다. 따라서 본인이 만들어서 확신이 있는 것 외의 클래스에는 memcpy를 쓰지 않는 것이 좋습니다.
Quote: ㅡ,.ㅡ;; 씀 (화,
C나 C++이나 배열 자체에는 전혀 차이가 없습니다.
그리고 그 특유의 맞춤법 띄어쓰기 무시하는거랑 횡설수설 두서없는 문장은 어떻게 좀 개선이 안됩니까? 하루 이틀도 아니고...
글쎄요 전 님처럼
글쎄요 전 님처럼 주둥질을 잘못해서..^^
근데 님은 앞뒤 글좀제대로 읽고 태클거세요..(차이가 없다라고 쓴글에 차이가없습니다 의 자다가 봉창두드리는 태클ㅡㅡ;; )
그리고 글쓰면서도 그렇게 자신이 부끄러워 익명으로트롤짓할꺼면 머하로 글쓰세요.^^
----------------------------------------------------------------------------
감사합니다.
많은 분들 덕분에 잘 이해했습니다.
이제는 안심하고 coding 하겠네요.
다차원 배열에 대해서
다차원배열의 개념에 혼란이 있으신 것 같습니다. 그리고 그 혼란의 원인은 배열과 포인터을 거의 같은 것으로 생각하시기 때문인 듯 합니다. 사실 언어 공부할 때 이 부분은 잘 안 가르쳐 주는 경우가 많지요.
C에서(C++도 마찬가지지만) 배열이라는 것은 단순히 어떤 단위가 메모리상에 일렬로 늘어서 있는 것입니다. 예컨대
int a[3] = {1, 2, 3};
이라고 하면, integer 1, integer 2, integer 3 이 일렬로 늘어서 있는 것입니다. 이 a의 type은 integer 3개의 배열으로서, 크기는 12byte(정수값이 4byte라면)입니다.
이 부분을 주의하셔야 하는데, 배열과 포인터는 전혀 다른 타입입니다.
int *b = a;
이 상황에서 b는 포인터이며, 그 type의 크기는 4byte 입니다.(플랫폼에 따라 다릅니다.) 반면 a는 앞서 말씀드리느 대로 12byte의 크기를 갖는 array입니다. b라는 포인터를 사용해서 b[0], b[1] 이라는 식으로 a의 데이터를 사용할 수는 있지만, type이라는 관점에서 보면 양자는 엄연히 다릅니다. 포인터라는 type에는 단순히 주소값만이 들어가지만, 배열이라는 type에는 데이터의 숫자가 포함됩니다.
이 점을 대개 프로그래밍 수업에서는 분명하게 인식시켜주지 않는데, 이는 배열과 포인터를 사실상 거의 같은 방식으로 사용하기 때문입니다. 하지만 이 개념을 분명히 하면 다차원 배열을 이해하는 것은 간단합니다.
int c[2][3];
이는 '정수값의 배열의 배열(array of array of integer)'입니다. 이 경우에는 integer 3개로 이루어진 배열 2개의 배열입니다. 즉 integer 3개짜리 배열 2개가 나란히 늘어서 있게 되므로 결과적으로 integer 6개가 늘어서게 됩니다.
int *d[2];
이는 '정수값의 포인터의 배열(array of pointer to integer)'입니다. 이는 pointer 2개가 늘어선 배열입니다. 각각의 포인터는 어딘가에 있는 정수값이나 정수값의 array의 주소를 포함하고 있겠지요.(물론 쓰레기값이 들어있을 수도 있습니다.)
위에서 배열이라는 type은 그 크기를 포함한다고 했는데, 사실 자기 스스로의 크기는 모를 수도 있습니다. 하지만 배열의 원소의 크기는 항상 알려져 있어야 하며, 또 모두 동일해야 합니다. 따라서
int c[][3]
이런 표현은 가능하지만
int c[2][]
이건 불가능합니다. 배열의 원소가 되어야 하는 array of integer의 크기가 정해지지 않았기 때문입니다. 이런 경우에는 array of pointer to integer를 사용해야 할 것입니다.
그러면, memcpy를 사용할 수 있는가 없는가의 문제도 더이상 고민할 필요가 없으시리라고 봅니다.
참고로, 배열은 개념적으로 다른 data type과 유사한 부분이 많지만, 한 가지 결정적인 차이가 있습니다. 컴파일러 내부에서는 array역시 pointer와 마찬가지로 주소값으로 사용한다는 점이지요. 앞의 코드로 돌아가 보겠습니다.
int a[3] = {1, 2, 3};
int *b = a; /* int *b = &a; 가 아님 */
이 점이 배열과 포인터의 개념에 혼란을 일으키는 주범일 것입니다.
그런데 이 부분에서도 약간의 차이는 있습니다. b는 엄연히 스스로 data type으로서 존재합니다. 이 경우 b는 stack상에 4byte의 메모리가 할당되어 있고, 그 공간에 a의 주소값을 기록하는 것이지만, a의 주소값은 컴파일러가 알고 있으며 따로 stack상에 기록되는 곳은 없습니다. stack 상에는 integer 3개를 위한 공간이 할당되어 있을 따름입니다.
이 때문에, &b 는 a의 주소값이 저장되어 있는 다른 위치의 주소값이라는 의미가 있지만, &a 에는 그런 의미가 없습니다. C의 문법에서는 &a의 값은 a 자체와 같은 것으로 정의되어 있습니다.
Quote: 이 때문에, &b
&a도 a라는 배열을 가리키는 포인터라는 점에서 &b와 비슷한 의미가 있죠.
그리고 &a의 값과 a 자체가 같다는 것은 의미가 없는 말입니다.
&a와 a는 비교가 가능한 관계가 아니니까요.
(a가 포인터형으로 변환(decayed)된 결과가 &a와 같은 값을 가진다는 의도로 하신
말씀이라는 것은 짐작하지만, 그렇다면 처음에 배열과 포인터를 다른 형으로 다루어야
한다는 말씀이 무색하게 되지요.)
a의 형은 int형 원소 3개를 갖는 배열, 즉 int[3]이고
&a의 형은 그러한 배열을 가리키는 포인터, 즉 int(*)[3]입니다.
요약하면 서로 다른 형의 값이 같다고 하는 것은 의미가 없다는 뜻입니다.
댓글 달기