void my_memcpy(void *s1, const void *s2, unsigned int size)
{
unsigned char *c1 = s1;
const unsigned char *c2 = s2;
/* while (size-- > 0) *c1++ = *c2++; 후위형 증감 연산자 사용 */
while (--size >= 0) *c1++ = *c2++;
}
그런데, size는 무부호 정수형이므로 --size >= 0)이라는 제어식은
항상 참이 되고, 결국 무한 루프에서 빠져나오지 못하게 됩니다.
결국 이런 경우에는 증감 연산자를 전위형으로는 사용할 수 없고
반드시 후위형으로 사용해야 한다는 결론에 이르게 되는데, 이 부분과
관련하여 저자이신 전웅님의 의도가 어떤 것이었는지 궁금합니다.
아마도 전위형 증감 연산자에 대한 부분을 모두 삭제하든지, 아니면
my_memcpy를 고쳐서 size를 unsigned int가 아닌 int로 선언해야 하는데,
어느 쪽으로도 쉽게 고치기는 어려울 것 같네요.
--
김승범
맞습니다. 해당 예제가 잘못된 것입니다. 지난번 지적해 주신 것과 함께 정
오표에 반영하도록 하겠습니다 - 개인적인 일이 많아 정오표 업데이트가 늦
어지고 있습니다.
>
> 결국 이런 경우에는 증감 연산자를 전위형으로는 사용할 수 없고
> 반드시 후위형으로 사용해야 한다는 결론에 이르게 되는데, 이 부분과
> 관련하여 저자이신 전웅님의 의도가 어떤 것이었는지 궁금합니다.
제 의도가 무엇인지는 분명합니다. 저는 개인적으로 증감 연산자 사용에서
거의 항상 후위형을 선호합니다. 하지만, 많은 경우 (특히, 영어권 사람들
에 의해 제작된 프로그램의 경우) 증감 연산자는 전위형으로 사용되고 있습
니다. 대부분의 경우에 전위형, 후위형의 성능 차이가 무의미함을 강조하기
위해 붙인 예입니다 - 즉, 해당 프로그램 소스와 제 의도가 필연적인 연관
을 갖는 것은 아닙니다. 회상해보면 아마도 memcpy() 와 관련된 예가 먼저
들어가 있던 자리에 전위형, 후위형 이야기를 나중에 추가하면서 size 의
데이터형이 무부호 정수형임을 간과했던 것 같습니다. 유사한 잘못이 있으
면 또 찾아주시길 부탁드립니다.
>
> 아마도 전위형 증감 연산자에 대한 부분을 모두 삭제하든지, 아니면
> my_memcpy를 고쳐서 size를 unsigned int가 아닌 int로 선언해야 하는데,
> 어느 쪽으로도 쉽게 고치기는 어려울 것 같네요.
>
해당 (전위형 증감 연산자를 사용하는) while 문을 이렇게 고칠까요? ;-)
size++;
while (--size > 0) *c1++ = *c2++;
아무래도 전위형을 위한 예를 다른 것으로 대체하고 my_malloc() 에서는
size 가 무부호 정수형임을 강조하는 다소 긴 이야기가 추가될 듯 합니다.
그럼, 좋은 지적 감사드립니다.
--
Jun, Woong (myco...@hanmail.net)
Dept. of Physics, Univ. of Seoul
Web : http://c-expert.uos.ac.kr
my_memcpy(s1, s2, UINT_MAX);
는 어떻게 하실려고요? :)
S Kim
무부호 정수형은 overflow 가 없으며 modulo 연산으로 정의됩니다.
그 예는 장난 삼아 적은 예가 아닙니다. :)
그럼...
"Jun Woong" <myco...@hanmail.net> wrote in message
news:b718pg$3ss$1...@news.hananet.net...
> "Kim, Seungtai" <stkim@Heartmore. Com> wrote in message
news:b718s2$8vp$1...@news.kreonet.re.kr...
> > "Jun Woong" <myco...@hanmail.net> wrote in message
> > news:b71858$3e3$1...@news.hananet.net...
> > [...]
> > > 해당 (전위형 증감 연산자를 사용하는) while 문을 이렇게 고칠까요? ;-)
> > >
> > > size++;
> > > while (--size > 0) *c1++ = *c2++;
> > >
> > [...]
> >
> > my_memcpy(s1, s2, UINT_MAX);
> >
> > 는 어떻게 하실려고요? :)
> >
>
> 무부호 정수형은 overflow 가 없으며 modulo 연산으로 정의됩니다.
>
> 그 예는 장난 삼아 적은 예가 아닙니다. :)
크. 제가 착각을...
그런데, 원 글이 무엇을 설명하고자 한 것인지 자세히 모르겠습니다만, 이와같은
방식이 후위형 연산자를 사용한 것과는 계산량의 차이가 있는데 이를 후위형
연산을 전위형 연산으로 대체할 예로 적당한 것인지 모르겠습니다.
S Kim
매개변수의 값이 더 이상 필요하지 않은 경우, 추가적인 루프 제어 변수를
도입하지 않고, 동일한 반복 횟수만을 얻고자 하는 경우입니다.
unsigned char = 1byte
unsigned int = 4byte
unsigned long = 8byte 라면
unsigned char *는 1byte씩 증가되고,
unsigned int *는 4byte씩 증가되고,
unsigned long *는 8byte씩 증가되면서 복사하기 때문에
위에서 unsigned char *로 선언하는 것과
unsigned int *또는 unsigned long *으로 선언하는 것과 속도에 차이가 있지
않나요?
물론 unsigned int *또는 unsigned long *으로 선언할 때엔 size를
4나 8의 배수로 맞추어야겠지요.
이렇게 속도 향상을 할 수도 있습니다만, 해당 CPU의 구조에 따라
달라집니다. 그리고 아무 고려없이 이 방법을 구현한다면 정렬 문제가 발생할
가능성이 높습니다.
han.comp.lang.c에서 다룰 주제는 아닌 듯 싶습니다. glibc 등의 해당
소스를 보시면 됩니다. 여러 무림 고수들이 머리를 싸매고 만든 것이니깐요.
--
박종대
--
"Money, when it was a matter of electronic exchange, meant nothing.
There was no feeling of either wealth or of poverty above a certain level.
The world was a matter of plastic cards and of slots, and all the world
transferred, transferred, transferred."
- from Isaac Asimov's "Gold" -
만약, my_memcpy() 로 전달된 포인터가 unsigned int 나 unsigned long 으
로 접근할 수 없는 위치에 정렬되어 있다면 문제를 일으킬 수 있습니다.
또한, 표준에 의해 unsigned char 는 모든 bit 를 값을 위해 사용하도록 정
의되고 있습니다 - 즉, 문제를 일으킬 수 있는 trap representation 을 갖
지 않습니다. 반면, unsigned int/long 은 표현 중 일부 비트를 padding
bit 로 사용할 수 있고, 이중 일부가 trap representation 으로 예약될 수
있습니다. 이 경우, 임의의 값을 unsigned int/long 으로 접근하는 것은
trap representation 을 건드려 undefined behvaior 를 일으킬 수 있습니다.
이와 같은 문제로 위의 예제에서 unsigned char 를 unsigned int/long 으로
대체하는 것은 해당 프로그램을 정의되지 않은 행동을 일으키는 것으로 만
들어 버립니다. 따라서, 임의의 bit pattern 을 접근하는 "일반적인" 프로
그램에서는 반드시 unsigned char 형을 사용해야만 합니다.
이와 관련된 내용은 이미 책에서 다루고 있는 바입니다 - p.363)
그리고 속도와 관련된 성능 문제는, 늘 말씀드리듯이, 추상적인 단계의 C
프로그램에서 충분한 profiling 이나 어셈블리어 혹은 기계어 수준의 관찰
없이 단정할 수 없는 문제입니다. super-scalar machine 의 컴파일러는 위
의 함수를 unsigned char/int/long 에 무관하게 동일한 성능을 보이도록 구
성할 수도 있는 것이며, unsigned char 를 사용한 위와 같은 구조를 패터닝
하여 더 나은 성능을 보이는 기계어로 번역할 수도 있는 것입니다. C 프로
그램은 어디까지나 추상적인 단계에서 프로그래머의 의도를 표현하는 수단
임을 잊지 마시고, 진정 해당 부분이 프로그램의 전체 성능에서 병목 현상
을 일으키는 부분임이 확실할 때에만, C 프로그램 혹은 기계어 수준의 대책
을 생각해야 합니다.
(IT 백두대간 C 언어, pp.31-36)
예를 들어, 오래전 Tom Duff 가 Lucas 에서 일하고 있을 때 고안해낸
Duff's device 는 일반적으로 기계어 수준에서 이루어지는 loop unrolling
이라는 optimization 기술을 C 로 표현한 것입니다. 물론, 그가 해당 기술
을 Usenet 에 포스팅한 이유는 switch 문의 "fall through" 특성이 유용하
게 사용될 수도 있음을 보이려는 것이었지만 다수의 사람들은 그 기술이
"모든" implemenation 에서 효율적인 것은 아니기에 무의미하다고 주장했습
니다. 물론, 이는 사실입니다. 다만, Tom Duff 가 Duff's device 를 고안해
낸 이유는 자신이 개발 중인 프로그램의 "해당" 부분이 "특정" 환경
(machine 이름은 잊어버렸습니다) 에서 병목 현상을 일으키기 때문이었고,
실제 해당 부분을 Duff's device 로 대체한 결과 2배 이상의 성능 향상을
얻었다고 합니다. 즉, 이와 같은 분석 과정 없이 C 프로그램의 특정 부분이
지대한 성능 저하 혹은 향상을 가져올 것이라는 추측은 결코 유의미하지 않
습니다.
(IT 백두대간 C 언어, pp.885-886)
물론, memcpy() 같은 기반 라이브러리 함수는 프로그램 전체에서 매우 빈번
하게 사용되는 것이고 이에 따라 많은 implementation 은 memcpy() 가 해당
machine 에서 가장 최적의 성능을 내도록 여러가지 아이디어를 쏟아 부을
것입니다 - 심지어 일부 라이브러리 함수는 아예 어셈블리어로 제작될 수도
있습니다. 이것이 표준이 strcpy(), memcpy() 등을 라이브러리로 제공해 주
는 이유이며, 우리가 따로 만들어 쓰지 않고 implementation 을 믿으며 그
라이브러리 함수를 사용하는 이유입니다.
그럼...
위의 예제에서,
const unsigned char *c2 = s2;
선언/초기화를 했음에도 구. c2의 포인터를 위치를 변경할려는 행위(?)가 올바른지요?
그리구, 밑에 설명하신 부분중에 'trap representation'에 대해서 좀 더 구체적인 설명을 해 주실수 있습니까? 처음듣는 용어라서....
그럼, 많은 조언 감사드리며...
c2 자체는 const가 아닙니다. c2가 가리키는 unsigned char가 const지요.
pointer to const xxx와 const pointer xxx를 구분하셔야 합니다. 만일
unsigned char *const c2 = s2;
라고 했다면 c2++라는 식을 사용할 수 없었겠지요.
>
> 그리구, 밑에 설명하신 부분중에 'trap representation'에 대해서 좀 더 구체적인 설명을 해 주실수 있습니까? 처음듣는 용어라서....
유효한 수치값을 갖지 않고, 사용시 trap을 일으키는 표현 정도로 이해하시면
괜찮을 듯싶습니다. 예컨대 1바이트가 9비트인 시스템에서, 그 중 8비트만
수치값으로 사용하고 나머지 한 비트는 무조건 0으로 채우며, 만일 이 비트가
1로 되어 있을 때 이 값을 읽어 연산에 사용하려 할 경우 trap이 일어난다면,
이 비트가 1로 되어 있는 것을 trap representation이라고 할 수 있겠지요.
>
> 그럼, 많은 조언 감사드리며...
>
> > [... 과도한 인용 ...]
꼭 필요한 부분만 인용해 주시고 나머지는 지워서 올려 주시면 좋겠습니다.
:)
--
김승범
구체적인 예를 들면, parity bit 를 도입한 implementation 을 생각해 볼
수 있습니다. 물론, 이렇게 정수형의 표현에 padding bit 를 도입한
implementation 을 일반적인 목적의 machine 에서 만나기는 어렵습니다 -
하지만, DSP 나 embedded system 에서는 가능합니다. 따라서, 그러한
padding bit 와 trap representation 을 "엄격히" 적용하면 다음과 같은 결
론을 얻을 수 있습니다.
int a[10];
memset(a, 0, sizeof(a));
a[0]; /* undefined behavior */
a[0] 은 padding bit 와 trap representation 의 가능성으로 정의되지 않은
값 (indeterminate value) 을 갖게 되며, 그 값에 접근하는 행동은
undefined behavior 를 일으킵니다.
"유일하게" unsigned char 형은 절대 padding bit 를 갖지 않는다고 정의됩
니다 - 즉, 모든 bit 가 값을 표현하는데 사용됩니다. 따라서, (바람직한
예는 아니지만)
int main(void)
{
int i; /* indeterminate value */
unsigned char uc; /* unspecified value */
i; /* undefined behavior */
uc; /* okay */
return uc - uc; /* same as return 0; */
}
여기서 unspecified value 는 indeterminate value 와는 달리 값의 집합이
trap representation 을 갖지 않는 경우에 사용합니다.
그럼...
(IT 백두대간 C 언어, p.181, p.188, p.363, p.694)