맴돌이를 한 번이라도 줄이는 프로그래밍...궁금합니다.

jo1413의 이미지

for(a=1;a<=3;printf("3.독립 형태 : a = %d \n", a), a++); /* 1번 보기 */

for(a=0;a<3;a++,printf("3.독립 형태 : a = %d\n", a)); /* 2번 보기 */

1번과 2번의 차이점은 무엇인가요?;;
지금 책을 보면서 공부하고 있는데, 이해하기가 힘드네요.

책 내용...

Quote:

1번 for문은 일단 a에 4라는 값이 대입된 후에 for문을 벗어났습니다. 이슬비가 일단 무거운 사과를 네 개씩이나 받은 뒤에 다음 일을 그만둔 셈이니 쓸 데 없이 힘을 낭비한 셈입니다. 아마도 이슬비는 사람 놀리냐고 화를 낼 지도 모릅니다. 세 개까지만 받으라고 하지 왜 네 개까지 받은 뒤에 네 개는 필요없다고 하냐고 따질 지 모릅니다.
그러므로 쓸 데 없이 네 개씩 받게 해서 이슬비를 열받게 하지 말고 세 개까지만 받도록 프로그램을 짜는 것이 좋습니다.

보기1과 보기2의 달라진 부분은 조건식의 변수 초기값과 실행부의 순서입니다. 이를 통해 a에 4를 대입하지 않았습니다. 그러나 결과는 같습니다.
보기2 두 번째 줄에서 a에 0을 대입했습니다. 이 점이 중요합니다. 보기1에서는 a에 1을 대입했습니다. 그래서 1부터 시작하다보니 4를 받게 된 겁니다. 반면 보기2에서는 a의 값을 0으로 설정했습니다.

그리고 실행부에서 a++ 명령이 printf() 함수보다 먼저 나왔습니다. 따라서 a++ 명령에 의해 a는 1이 되었고 이어 나오는 printf() 함수를 통해 a의 값이 1로 출력됩니다. 이렇게 하면 a는 3개까지만 대입되고 printf() 함수를 통해 3이 출려된 다음에, a<3 조건에 의해 for문을 벗어납니다. 따라서 4를 받지 않습니다.

두 번째 줄 문장을 이슬비의 경우로 설명하면 이런 명령이 됩니다.

'이슬비야! 너 바구니는 지금 비어있는데, 세 개보다 작은 갯수를 받을 때까지만 내가 시키는데로 하면 돼.' 그리고 나서 한강물이 이슬비한테 a++을 계산해서 이슬비 바구니에 한 개를 던져줍니다. 그리고는 지금 받은 것이 세 개보다 작으면 칠판에 이슬비가 받을 사과 갯수를 쓰라고 합니다. 이슬비는 '응 지금 한 개 받았어.' 하고 쓸 겁니다.

다시 한강물이 물어봅니다.

'이슬비야! 세 개보다 적으면 말해라.'

이슬비는 현재 한 개를 받았으므로 '응! 아직 세 개보다 적어!' 하고 말합니다. 그러면 한강물이 이번에는 두 개를 던져줍니다. 이슬비는 '응. 이번에는 두 개가 왔네.' 하고 칠판에 두 개라고 적습니다.
다시 한강물이 물어봅니다.

'이슬비야! 세 개보다 적으면 말해라.'

이슬비는 현재 두 개를 받았으므로 '응! 아직 세 개보다 적어!' 하고 말합니다. 그러면 한강물이 이번에는 아까 던진 것에 하나를 더해서(a++) 세 개를 던져줍니다. 이슬비는 '응. 이번에는 세 개가 왔네.' 하고 칠판에 세 개라고 적습니다.
다시 한강물이 물어봅니다.

'이슬비야! 세 개보다 적으면 말해라.'

이슬비가 조금 전에 한강물로부터 받은 것은 세 개입니다. 그러므로 이 장면에서 이슬비는 '어? 지금 받는 것이 세 개니까, 세 개보다 적지 않은데?"하고 대답할 겁니다. 그러면 한강물은 '그럼 더 던질 필요가 없구나'하고 for문을 끝낼 겁니다. 이렇게 하면 이슬비가 쓸 데 없이 무거운 사과를 네 개나 받을 필요가 없어지는 셈입니다.

보기1과 보기2는 별 다른 차이가 없는 예제입니다. 그렇지만 어떤 경우에는 조건식이나 실행부의 순서에 따라서 맴돌이를 한 번이라도 더 할 수 있는 경우가 생깁니다. 이는 비효율적인 프로그램입니다. 가능한 한 번이라도 맴돌이를 덜 돌리고, 가능한 변수에 대입하는 과정을 한 번이라도 더 줄여주어야 합니다. 이것이 바로 프로그램을 잘 짜는 사람의 특징입니다.

주변의 예를 들자면 하루 2천만 명에게 우편물을 발송하는 한 회사에서 자료를 출력하는데 7시간이 걸렸던 일이 있는데 데이터베이스에 능통한 사람에게 자문을 구해서 프로그램의 구조를 바꾼 결과 불과 2초만에 출력이 나오는 것으로 바뀐 사례가 있습니다. 결과는 같은데 결과가 나오기까지의 시간은 무려 몇 만배나 차이난 것입니다. 하루면 끝날 일을 몇 만일 동안 해야한다고 생각해 보십시오. 끔찍한 일입니다.

훌륭한 프로그래머는 같은 결과를 얻기 위한 프로그램 실행 시간이 짧고, 실행 파일의 크기가 작으며, 소스 파일을 알기 쉽게 짜는 사람입니다. 프로그램을 공부하는 분이라면 늘 이점을 염두에 두고 좀 더 효율적이고, 빠르고, 작으며, 알기 쉬운 소스 파일을 작성하도록 노력해야 합니다.

보기2는 a<3 조건에 의해 for문을 벗어납니다. 따라서 4를 받지 않습니다... 이러는데요. 보기1과의 차이점을 분명히 좀 알려주세요.
지은이님께서 쉽게 설명해 놓으신듯 한데 ㅠㅠ 제가 이해를 못하네요.
저는 그게 그거 같거든요;;
그래도! 훌륭한 프로그래머가 되려면*^^*

익명 사용자의 이미지

for(a=0;a<3;a++,printf("3.독립 형태 : a = %d\n", a)); /* 2번 보기 */

나중에 버그 찾으려면 머리 터집니다. 가독성을 높이세요..

for(a=0;a<3;a++)
   printf("3.독립 형태 : a = %d\n", a)); /* 2번 보기 */

정 위와 같이 쓰실려면
for(a=0;a<3;printf("3.독립 형태 : a = %d\n", a++)); /* 2번 보기 */

이게 나을듯

jo1413의 이미지

아...
써놓고 생각해보니...

보기1에서는 printf로 3을 출력한 다음에 a++에 의해서 4가 되네요??
보기2는 a++로 3되고 printf로 출력한 다음에 a<3 때문에 끝나고요.

제 생각이 맞는건가요?

------------------------------------------------------------
Get busy living, or get busy dying.

elien의 이미지

for(a=0;a<3;a++,printf("3.독립 형태 : a = %d\n", a)); /* 2번 보기 */

손님 wrote:
for(a=0;a<3;printf("3.독립 형태 : a = %d\n", a++)); /* 2번 보기 */
는 결과가 다릅니다. ++a 로 하면 모를까...

1번과 2번의 차이점은, for loop 의 실행이 완료된 다음의 변수 a 가 가지는 값이 의도하지 않은 것이 된다... 는 것이 골자인 것 같은데요.

p.s 어딘가의 강좌인가요? 자칫 프로그래밍 버릇 제대로 들겠는걸요 -_-;

훗, 못 믿겠나?

creativeidler의 이미지

웬만하면 최적화 단계에서 저런 정도는 처리 될 겁니다. 윗분들 말씀처럼 가독성 높은 코드가 최고입니다.

jo1413의 이미지

elien wrote:
for(a=0;a<3;a++,printf("3.독립 형태 : a = %d\n", a)); /* 2번 보기 */

손님 wrote:
for(a=0;a<3;printf("3.독립 형태 : a = %d\n", a++)); /* 2번 보기 */

는 결과가 다릅니다. ++a 로 하면 모를까...

1번과 2번의 차이점은, for loop 의 실행이 완료된 다음의 변수 a 가 가지는 값이 의도하지 않은 것이 된다... 는 것이 골자인 것 같은데요.

p.s 어딘가의 강좌인가요? 자칫 프로그래밍 버릇 제대로 들겠는걸요 -_-;

첫번째에.... a<=3 입니다^^;; a<3은 값이 틀리지만 a<=3이면 두번째 보기와 값이 같지요.
p.s로 쓰신게...버릇 좋게 든다는 거죠??
책에서 보고 배우는 중이에요.
'컴맹도 쉽게 배우는 C언어 이야기, 김중태'
정말 쉽게 쓰셔서 김중태님 애독자가 됐습니다^^;;
시간이 되시거든
http://www.dal.co.kr 가보세요^^

가독성 높이는게 최고군요...
찾아보니까 이런 글이 있군요.
http://network.hanbitbook.co.kr/view.php?bi_id=921

------------------------------------------------------------
Get busy living, or get busy dying.

jenix의 이미지

p.s. 는 나쁘게 든다고 말씀하신거같은데요 -_-;;
그쪽 강좌에서 코드를 저렇게 기술해놓았다면
절대 비추입니다 :twisted:

---------------------------------------------------------------------------
http://jinhyung.org -- 방문해 보세요!! Jenix 의 블로그입니다! :D

jo1413의 이미지

제가 예를 든 예제는 일부분 입니다.

원래는 3가지 예제가 있습니다.

(형식.1) for문으로만 처리할 때

[사용 형식.1]
for(변수에 값 대입,변수에 값 대입,....; 조건식; 명령문 실행, 명령문 실행,...);

[보기1]
for(a=1;a<=3;printf(" a = %d \n", a), a++);

(형식.2)for문 다음에 한 줄의 문장만 이용할 때

[사용 형식.2]
for(변수에 값 대입, 변수에 값 대입,...,; 조건식; 명령문 실행, 명령문 실행,...)
명령문;

[보기2]
for(a=1;a<=3;a+=1) 
  printf("a = %d \n", a);

(형식.3) for문 다음의 명령을 블록(복문)으로 처리할 때
[사용 형식.3]
for(변수에 값 대입, 변수에 값 대입,...; 조건식; 명령문 실행, 명령문 실행,..)
{
  명령문;
  명령문;
  ...
}

[보기3]
for(a=1;a<=3;)
{
  printf("a = %d \n", a);
  a=a+1;
}

제가 궁금한 부분만 예제를 써놓은 겁니다.
나쁘게 든다고 말씀하신 건가요?
자칫이랑 제대로란 단어 때문에 혼란이 오네요;;;

------------------------------------------------------------
Get busy living, or get busy dying.

jenix의 이미지

코딩스타일이 개개인마다 차이가 있겠지만..
보통은 [보기2] , [보기3] 으로 표기하지요.
for 문 안에 직접 명령문을 넣어버리는건 가독성이 너무나 떨어지기때문에요..

커니건의 The Practice of Programming 이란 책을 한번 읽어보시길 권해드립니다 :)

---------------------------------------------------------------------------
http://jinhyung.org -- 방문해 보세요!! Jenix 의 블로그입니다! :D

elien의 이미지

jo1413 wrote:
제가 궁금한 부분만 예제를 써놓은 겁니다.
나쁘게 든다고 말씀하신 건가요?
자칫이랑 제대로란 단어 때문에 혼란이 오네요;;;

끄응; 헷갈리셨나요?
나름대로 반어법을 활용한 강조랍시고 써봤습니다만;

책에 있는 예제의 일부라니 다행이네요.
책의 예제는 말 그대로 예제일 뿐이니까요, 코딩하실 때도 무조건 책에 있는 것을 따라하기 보다는 자신만의 스타일을 갖춰나가시길 바래요~
(물론 바람직한 방향으로 :wink: )

훗, 못 믿겠나?

jo1413의 이미지

네, 답변들 감사합니다.
커니건의 The Practice of Programming 한 번 봐야겠네요^^

------------------------------------------------------------
Get busy living, or get busy dying.

moonzoo의 이미지

제가 보기엔 매우 안좋은 코드같습니다.

보기1이나 보기2이나 똑같이 printf을 세번하게 되구요.

for문에서 평가는 4번거칩니다.(4번째에 빠져나오겠죠..)

도대체 어디서 맴돌이(루프수?) 를 줄였다는 건지 제 짧은 소견으로는

이해가 안되는군요.

이해하기 어려운 코드는 좋은 코드가 아닙니다.

가독성에 먼저 중점을 두세요.

웬만한 최적화는 컴파일러 수준에서 해줍니다.

addnull의 이미지

음.. 잠시만요?
제가 알기론,

for (i=0 ; i<=n ; statement1, statement2);

이렇게 할 경우.
statement1 이 statement2 보다 먼저 실행될 것인지 여부는 컴파일러마다 다른 걸로 알고 있습니다.
마찮가지로.

result = pop()-pop();

역시 왼쪽 pop이 오른쪽 pop보다 먼저 실행된다는 보장이 없죠.
The C Progamming Language에서 나왔던 내용 같은데 지금 옆에 책이 없어서 확인은 못해봤습니다.
책 저자가 말하길 "당신의 정신 건강을 위해서 저런 방식의 코딩을 절대 하지 마라."라고 강력히 권고했던 걸로 기억이.. =_=a

jo1413 wrote:
책 내용...
Quote:
'이슬비야! 너 바구니는 지금 비어있는데, 세 개보다 작은 갯수를 받을 때까지만 내가 시키는데로 하면 돼.' 그리고 나서 한강물이 이슬비한테 a++을 계산해서 이슬비 바구니에 한 개를 던져줍니다. 그리고는 지금 받은 것이 세 개보다 작으면 칠판에 이슬비가 받을 사과 갯수를 쓰라고 합니다. 이슬비는 '응 지금 한 개 받았어.' 하고 쓸 겁니다.

다시 한강물이 물어봅니다.

'이슬비야! 세 개보다 적으면 말해라.'

음... 마음이 삭막해져서 그런지. 읽다보니 정신이 혼란스러워지네요 ^^;;

2005년 10월 27일.

asiawide의 이미지

프로그래밍 언어 시험에나 나올법한 정말 예제를 위한 예제입니다. 무시하셔도 좋고요. -_-; 저런 이유 때문에 7시간 걸리는게 2초 걸리는 그런 경우는 거의 없다고 보셔도 좋을 것입니다. 7시간 걸리는게 한 6시간 50분쯤 걸리겠죠.

속도가 문제가 되는 경우는 알고리즘의 복잡도가 문제가 되는 경우가 대부분이고요.

특수한 경우로 C에서 포트란 모듈을 불러올 때 배열의 메모리 구조때문에 속도가 떨어지는 경우처럼 근본적인 설계 차이로 발생하는(http://www.math.utah.edu/software/c-with-fortran.html#array-storage-diffs) 문제가 있습니다.
[/url]

익명 사용자의 이미지

제 생각엔 질문하신것에 두가지 문제가 합께 합쳐져 있는것
같습니다.

1. for 루프에서의 식평가 순서
2. index를 0부터 시작하느냐 1부터 시작하느냐

1의 문제는 코드가독성, C언어 SPEC, 그리고 특정 컴파일러의
구현과 연관되어 있고..위에서 이미 많은 분들이 지적했던
사항입니다.

그런데 2문제는 위에서 아무도 지적하시지 않으셨네요.
이것을 특별히 문제라고 할것까지는 없을 수 있겠으나...
C를 사용하는 사람으로서는 한번 고민해봐야 할 문제가
확실합니다.

이것을 어떤책에서는 일명 loop invariant 라고 소개하고 있는데..
일단 그 개념을 떠나서.. C언어에서는

(1) 배열첨자등의 경우때문에 0부터 인덱싱을 하는것이 편리하다.
(2) 0부터 인덱싱을 하면 루프 탈출 조건식이 간단해진다.
(3) 0부터 인덱싱 하는것이 수학적으로도 명료하다.

라는 잇점들이 있습니다.
그래서 루프 인덱싱은 특별한 경우가 아니면 대부분 0부터 시작
합니다.

아마 1과 2의 문제가 합쳐져서 질문자가 더 헷갈려 하시는것 같군요.

asteroid의 이미지

어니스트 wrote:
음.. 잠시만요?
제가 알기론,

for (i=0 ; i<=n ; statement1, statement2);

이렇게 할 경우.
statement1 이 statement2 보다 먼저 실행될 것인지 여부는 컴파일러마다 다른 걸로 알고 있습니다.
마찮가지로.

result = pop()-pop();

역시 왼쪽 pop이 오른쪽 pop보다 먼저 실행된다는 보장이 없죠.
The C Progamming Language에서 나왔던 내용 같은데 지금 옆에 책이 없어서 확인은 못해봤습니다.
책 저자가 말하길 "당신의 정신 건강을 위해서 저런 방식의 코딩을 절대 하지 마라."라고 강력히 권고했던 걸로 기억이.. =_=a

피연산자의 위치가 실행순서와 관계 없다는 게 정말인가요? 아무리 컴파일러가 최적화를

수행한다고 하지만, 위의 pop의 코드에서 순서는 연산의 결과를 바꿀정도인데요;;;

pop이 macro가 아닌 들, 어떤 이론으로 실행 순서를 컴파일러 임의대로 변경할 수 있는지 궁금합니다.

addnull의 이미지

asteroid wrote:
피연산자의 위치가 실행순서와 관계 없다는 게 정말인가요? 아무리 컴파일러가 최적화를
수행한다고 하지만, 위의 pop의 코드에서 순서는 연산의 결과를 바꿀정도인데요;;;
pop이 macro가 아닌 들, 어떤 이론으로 실행 순서를 컴파일러 임의대로 변경할 수 있는지 궁금합니다.

C언어 규약에서
operator 우선순위는 있어도
operand 우선순위는 없다고 합니다.

컴파일러 만드는 사람 마음대로라는거죠.
그리고 우선순위는 최적화하고는 연관이 없는 것 같은데요 ^^?

2005년 10월 27일.

죠커의 이미지

어니스트 wrote:
음.. 잠시만요?
제가 알기론,

for (i=0 ; i<=n ; statement1, statement2);

이렇게 할 경우.
statement1 이 statement2 보다 먼저 실행될 것인지 여부는 컴파일러마다 다른 걸로 알고 있습니다.
마찮가지로.

아닙니다. 위와 같이 콤마가 콤마 연산자인 경우에 왼쪽 부터 오른 쪽으로 수행된다는 것이 표준에 의해 보장되어 있습니다.

어니스트 wrote:
result = pop()-pop();

역시 왼쪽 pop이 오른쪽 pop보다 먼저 실행된다는 보장이 없죠.

함수호출이 포함되었기 때문에 둘의 sequence point는 다릅니다. 그래서 문제가 생길 이유가 없습니다.

문제가 생길 가능성이 있는 코드는 아래와 같은 코드입니다.

int i = 10;
int j = i++ + ++i + i++ + ++i;

이 경우에는 하나의 sequence point 동안에 storage를 여러번 변경했습니다. 이 경우의 값은 보장되지 않습니다.

int i = 10;
func(++i, ++i);

함수의 전달인자의 평가 순서는 정해져 있지 않습니다. 이 경우는 콤마 연산자가 아니기 때문에 문제가 됩니다.

죠커의 이미지

jo1413 wrote:
for(a=1;a<=3;printf("3.독립 형태 : a = %d \n", a), a++); /* 1번 보기 */

for(a=0;a<3;a++,printf("3.독립 형태 : a = %d\n", a)); /* 2번 보기 */

1번과 2번의 차이점은 무엇인가요?;;
지금 책을 보면서 공부하고 있는데, 이해하기가 힘드네요.

반복의 횟수는 둘 다 동일합니다. 차이는 1,2,3 세는 것과 0,1,2 세는 것의 차이입니다. 출력되는 메시지는 둘다 1,2,3이 되겠네요.

표준에 맞는 코드이지만 3번째로 들어간 printf와 a++의 위치가 달라짐에 따라서 결과가 바뀔 수 있는 코드이기 때문에 좋지 않은 선택이라고 봅니다.

덧붙여 C++을 공부하고 계신다면 a++보다는 ++a를 사용하는 것이 좋고 < 보다는 !=를 사용하는 것이 좋습니다. C에서는 a++과 ++a이 거의 동일하게 최적화되고 있지만 C++의 반복자는 일반적으로 ++a을 만들어서 a++을 구현하고 있고 이 부분은 최적화의 대상이 아닙니다. 또한 <도 반복자를 사용함에 있어서 안전하지 못할 가능성이 있기 때문에 !=를 이용하는 것이 보다 generic한 방법일 수 있습니다.

for(int a = 0; a != 3; ++a) {
    printf("3.독립 형태 : a = %d\n", a + 1); /* 2번 보기 */
}

물론 그것 보다는 STL의 알고리즘을 더 추천드립니다.

익명 사용자의 이미지

CN wrote:

함수의 전달인자의 평가 순서는 정해져 있지 않습니다. 이 경우는 콤마 연산자가 아니기 때문에 문제가 됩니다.

전 이제까지 함수의 매개 변수는 콤마 연산자로
구분되고, 매개변수 전달은 목적코드 생성기에서
알아서 하는 줄 알았는데, 아니었던가 보군요 ㅠ.ㅠ
cinsk의 이미지

골치 아픈? topic이 나왔군요. (참고로 제 나름대로 골치아픈 topic은, sequence point, signed/unsigned, conversion등입니다. :wink: )

결론부터 말하면, C 언어에 연산자 우선 순위(operator precedency)가 있다는 것은 다 알 겁니다.

그 다음으로, 여러분이 알아두어야 할 내용은 side effect입니다. 식(expression)은 그 자체로 어떤 값으로 평가되고, side effect를 가질 수 있습니다. 예를 들어 "a = 1"이란 식은 1로 평가되고, "변수 a에 1을 저장한다"는 side effect를 가집니다.

문제는 이 side effect가 언제 반영되느냐인데 (즉 위의 예에서 언제 변수 a가 저장되어 있는 메모리에 1이 써지느냐), 어떤 식(expression)의 side effect는, sequence point가 지나면 무조건 제대로 끝나야 합니다.

여기서 sequence point라는게 뭔가라는 질문이 나올 수 있는데, 이건 조금 뒤로 넘기기로 하겠습니다. (하나만 짚고 넘어가면, ';'으로 구별되는 statement도 sequence point가 될 수 있습니다.)

예를 들어 다음 코드를 봅시다:

int i, j;

i = 3;
j = i + 1;

위의 "i = 3"에서, 실제 i에 3이 저장되는 side effect가 언제 수행되느냐가 관건인입니다. 답은, 이 side effect를 포함한 expression 바로 다음에 나오는 sequence point에서 i에 3이 저장되는 작업이 끝난다이며, 여기에서 sequence point는 "i = 3;"이 끝나는 지점입니다. 즉 간단히 ';'가 나왔을 때라고 할 수 있겠습니다.

그렇기 때문에 우리는 "j = i + 1"을 봤을 때, "아~ i에 저장된 3과 1을 더해서 4라는 값이 j에 저장되겠구나"라고 생각할 수 있는 겁니다.

이제 sequence point라는 개념에 어느 정도 익숙해 졌을 거라고 기대하면서? C 언어에 존재하는 sequence point에는 어떤 것들이 있는지 알아보면 다음과 같습니다:

첫째, 함수 호출에서, 함수에 전달된 모든 인자가 평가된 다음.
둘째, "&&", "||", "?" (? :), "," (comma) 연산자의 첫번째 피연산자의 끝.
세째, 완전한 선언(full declarator)의 끝
네째, 완전한 식(full expression)의 끝 -- 여기에는 expression statement, if, switch, while, for에 나오는 (...)의 끝, return에 쓰이는 expression도 포함.
다섯째, library function이 리턴한 바로 그 순간.
여섯째, formatted io function의 conversion specifier에 대한 작업이 끝난 바로 다음 (예: printf와 scanf)
일곱째, bsearch()나 qsort()에 전달된 비교 함수가 시작되기 바로 전과 바로 끝난 순간.

따라서, 위 두번째 규칙에 의해서,

for (i=0 ; i<=n ; statement1, statement2);

에서 statement1은, statement2보다 먼저 수행됩니다. (즉, "," 연산자의 첫번째 operand가 평가된 다음에 sequece point가 있기 때문)

그러나 함수 호출에서

foo(arg1, arg2)

에서 arg1과 arg2는, 어느 것이 먼저 평가되는지 알 수 없습니다. (왜냐하면 첫번째 규칙에 의해, 모든 인자가 다 평가된 순간이 sequence point이므로)
addnull의 이미지

CN님과 cinsk님 글을 읽어보았습니다. (글타래가 너무 길어질 것 같아서 인용은 생략합니다 ^^)
음... sequence point는 잘 모릅니다만, cinsk님의 설명을 읽어보니 그것과는 조금 다른 이야기인 것 같네요.

for (i=0 ; i==0 ; j=++i, k=++i);
printf("%d %d\n", j, k);

result = (i=pop())-(j=pop());
printf("%d %d\n", i, j);

이렇게 하면 좀 더 문제가 명확해질까요?

첫번째 코드의 실행결과는 "1 2" 또는 "2 1" 둘 중에 하나겠지요.
"," 연산자가 연산하기 전에 우선 좌측, 우측의 오퍼랜드들의 값이 결정되어야 하는데
좌우 우선순위는 없는 걸로 알고 있습니다.

마찬가지로 두번째 코드에서 "-" 연산자 좌우의 함수호출 중
어느 것이 먼저 되어야한다는 규칙은 없는 걸로 알고 있습니다.
그렇기 때문에 문제가 발생되는 거죠.

"The C Programming Language" 내용을 검색해보니 안나오네요... OTL
다만, 해당 예제는 올라와있습니다.
http://users.powernet.co.uk/eton/kandr2/krx406.html

하지만 확실한 결론은 이런 side effect 가능성이 있는 코드는 최대한 피하는게 좋겠죠 ^^
저도 오퍼랜드 간의 독립성은 철저히 지키는게 좋습니다.

2005년 10월 27일.

lovewar의 이미지

cinsk wrote:

그 다음으로, 여러분이 알아두어야 할 내용은 side effect입니다. 식(expression)은 그 자체로 어떤 값으로 평가되고, side effect를 가질 수 있습니다. 예를 들어 "a = 1"이란 식은 1로 평가되고, "변수 a에 1을 저장한다"는 side effect를 가집니다.

이부분에 대해 부연설명을 합니다.

대입문(Assignment expression)은 수식에 포함되며 수식은 평가된 값을 갖습니다.
여기서, 대입문의 주 행위(effect)는 평가된 값을 갖는 것입니다.
그리고 부가적 행위(side effect)는 값을 저장하는 행위입니다.

-- 덧붙이는 글 --
잘못된 부분이 있으면 지적해 주시기 바랍니다.

소타의 이미지

그럼 이건 어떤가요?

for (i=10; i--;)
{
  printf("%d ", i);
}

결과는 9 8 7 6 5 4 3 2 1 0 이라고 찍힙니다.

순서대로 찍히지는 않습니다. 순서를 무시한다면 위의 for 문의 성능은 어떨런지..
저는 순서를 지키지 않아도 될 때는 항상 for 문을 저렇게 사용합니다. 좀 고전적인 문서였지만 저렇게 하면 가장 성능이 좋다고 들었습니다. i가 1일 때 마지막으로 루프 돌고 다음 루프에서 비교할 때 바로 종료를..
물론 그 후에 컴파일러들이 영리해져서 똑같다는 말도 들었지만요..

ixevexi의 이미지

아닙니다. 위와 같이 콤마가 콤마 연산자인 경우에 왼쪽 부터 오른 쪽으로 수행된다는 것이 표준에 의해 보장되어 있습니다. 

어니스트 씀: 
코드: 
result = pop()-pop(); 


역시 왼쪽 pop이 오른쪽 pop보다 먼저 실행된다는 보장이 없죠. 


함수호출이 포함되었기 때문에 둘의 sequence point는 다릅니다. 그래서 문제가 생길 이유가 없습니다. 

문제가 생길 가능성이 있는 코드는 아래와 같은 코드입니다. 

문제가 생길 이유가 없다는 것에 대하여 잘 모르겠네요 ^^
두개의 시퀀스 포인트는 다릅니다.

그러나 operand가 평가되는 순서는 임플리멘테이션에 의존하기 때문에
뒤에껏이 먼저 실행이 되나 앞에 껏이 먼저 실행이 되는 것은 역시 아무도 모릅니다.
해보지 않는이상.. 역시 이식성 없는 코드죠..

C++, 그리고 C++....
죽어도 C++

asteroid의 이미지

어니스트님이 언급하신 부분 중,

for (i=0 ; i==0 ; j=++i, k=++i); 
printf("%d %d\n", j, k);

j=++i은 k=++i가 수행되기 전에 ,라는 sequence point를 거치므로,

결과는 항상 1 2가 되어야 옳은 것이죠?

result = (i=pop())-(j=pop()); 
printf("%d %d\n", i, j);

이 경우엔, i=pop()과 j=pop(), 둘 중 어느 것이 먼저 수행될 지

정의되지 않았으므로, 결과값을 예측할 수 없다는 것이 맞구요.

sephiron의 이미지

lovewar wrote:
cinsk wrote:

그 다음으로, 여러분이 알아두어야 할 내용은 side effect입니다. 식(expression)은 그 자체로 어떤 값으로 평가되고, side effect를 가질 수 있습니다. 예를 들어 "a = 1"이란 식은 1로 평가되고, "변수 a에 1을 저장한다"는 side effect를 가집니다.

이부분에 대해 부연설명을 합니다.

대입문(Assignment expression)은 수식에 포함되며 수식은 평가된 값을 갖습니다.
여기서, 대입문의 주 행위(effect)는 평가된 값을 갖는 것입니다.
그리고 부가적 행위(side effect)는 값을 저장하는 행위입니다.

-- 덧붙이는 글 --
잘못된 부분이 있으면 지적해 주시기 바랍니다.

printf("%d",a=1+1)

의 경우 대입문 "a=1+1"의 주 행위로 인하여 %d가 인자를 받을 수 있는 것이고
부 행위로 인하여 a 변수에 2가 저장되는 것인가요?[/code]

addnull의 이미지

이런.. 저 때문에 상당히 복잡한 이야기까지 진행된 것 같군요 ;;;
sequence point에 대해서 찾아보다가 다음 문서를 찾았습니다.

http://www.cinsk.org/cfaqs/html/node5.html

Quote:
Evaluation Order
복잡한 expression (수식) 안에서 subexpression을 (부분식) 평하가는 순서는 완전히 컴파일러 마음대로입니다; 이 순서는 여러분이 생각하는 operator precedence와는 (연산자 우선 순위) 별 상관이 없습니다. 여러개의 보이는 부작용이 (multiple visible side effects) 없거나, 한 변수에 여러 개의 side effect가 평행하게 (parallel) 작용하지 않는 한 컴파일러의 평가 순서는 생각할 필요가 없습니다. 그렇지 않다면 이러한 경우, 컴파일러의 행동은 정의되어 있지 않을 수 있습니다. (The behavior may be undefined.)

문서를 읽어보니 제가 지적한 문제점이 sequence point하고 연관된 거였군요.
그리고

for (i=0 ; i==0 ; j=++i, k=++i);
printf("%d %d\n", j, k);

에서 콤마연산자는 왼쪽 오퍼랜드가 먼저 실행되는게 보장되어있다고하니
결과는 항상 "1 2"입니다.

result = (i=pop())-(j=pop());
printf("%d %d\n", i, j);

이것은 좌측 pop()이 먼저되는지 우측 pop()이 먼저되는지 보장되어있지 않습니다.

모든 오퍼래이터의 오퍼랜드 우선순위가 정해져있지 않은 건 아니군요 :oops:

2005년 10월 27일.

죠커의 이미지

소타 wrote:
그럼 이건 어떤가요?
for (i=10; i--;)
{
  printf("%d ", i);
}

결과는 9 8 7 6 5 4 3 2 1 0 이라고 찍힙니다.

순서대로 찍히지는 않습니다. 순서를 무시한다면 위의 for 문의 성능은 어떨런지..
저는 순서를 지키지 않아도 될 때는 항상 for 문을 저렇게 사용합니다. 좀 고전적인 문서였지만 저렇게 하면 가장 성능이 좋다고 들었습니다. i가 1일 때 마지막으로 루프 돌고 다음 루프에서 비교할 때 바로 종료를..
물론 그 후에 컴파일러들이 영리해져서 똑같다는 말도 들었지만요..

pod에는 의미가 없는 "이른 최적화"이겠지만 --i가 되는게 더 낫겠지요.

ixevexi wrote:
그러나 operand가 평가되는 순서는 임플리멘테이션에 의존하기 때문에
뒤에껏이 먼저 실행이 되나 앞에 껏이 먼저 실행이 되는 것은 역시 아무도 모릅니다.
해보지 않는이상.. 역시 이식성 없는 코드죠..

맞습니다. 내가 실수 했습니다. ixevexi님과 asteroid님의 지적에 감사드립니다.

asteroid wrote:
for (i=10; i--;) 
{ 
  printf("%d ", i); 
}

이런 코드는 i가 signed일 때만 올바르게 동작하지 않습니까?

unsigned int에서도 문제될 부분은 없어 보입니다. signed이든 unsigned이든 0은 있습니다.

sephiron wrote:
printf("%d",a=1+1)

의 경우 대입문 "a=1+1"의 주 행위로 인하여 %d가 인자를 받을 수 있는 것이고
부 행위로 인하여 a 변수에 2가 저장되는 것인가요?[/code]

맞습니다. 덧붙여 a = b에서 a와 b 모두 함수 일 경우 (예를 들어 a가 포인트 형을 반환하는 함수일 경우) b가 a보다 먼저 수행되는 것이 chain rule에 의해 실제로 보장되고 있습니다. 왜냐하면 b가 평가되지 않은 상태에서 a에 대입되는 것은 위험하고 비상식적인 행동이기 때문입니다. 이 경우는 예외적으로 평가 순서가 명시된 경우라고 볼 수 있겠지요.

어니스트 wrote:
모든 오퍼래이터의 오퍼랜드 우선순위가 정해져있지 않은 건 아니군요 :oops:

콤마 오퍼레이션의 경우에는 sequence point에 의한 것이라는게 정확한 설명이겠지요. 지나치게 sequence point에 신경을 쓰다보니 글을 잘못적게 되어 논의가 길어지게 되어 죄송합니다.

lovewar의 이미지

sephiron wrote:

printf("%d",a=1+1)

의 경우 대입문 "a=1+1"의 주 행위로 인하여 %d가 인자를 받을 수 있는 것이고
부 행위로 인하여 a 변수에 2가 저장되는 것인가요?

녜,

추가적으로 설명하면,

다음은 표준문서에 나오는 설명의 일부입니다.

Quote:

An assignment expression has the value of the left operand after the assignment, but is not an lvalue

이 문장을 유추하면 계산된 값은 임시공간(register)에 있다.
라고 생각할 수 있습니다.
하지만, 구현체가 어떻게 구현하는냐에 따라 달라질 것입니다.
doldori의 이미지

CN wrote:
덧붙여 a = b에서 a와 b 모두 함수 일 경우 (예를 들어 a가 포인트 형을 반환하는 함수일 경우) b가 a보다 먼저 수행되는 것이 chain rule에 의해 실제로 보장되고 있습니다. 왜냐하면 b가 평가되지 않은 상태에서 a에 대입되는 것은 위험하고 비상식적인 행동이기 때문입니다. 이 경우는 예외적으로 평가 순서가 명시된 경우라고 볼 수 있겠지요.

그렇지 않습니다. a가 포인터를 반환한다 하더라도 반환값 자체는 임시 개체이므로
lvalue가 될 수 없고 따라서 =의 좌변에 올 수 없습니다. 이런 것은 가능하겠죠.
int* f(void);
int* g(void);

*f() = *g();

하지만 =의 피연산자의 평가 순서는 정해져 있지 않으므로 이 경우에도 g()가 f()보다
먼저 호출된다는 보장은 없습니다.
죠커의 이미지

doldori wrote:
CN wrote:
덧붙여 a = b에서 a와 b 모두 함수 일 경우 (예를 들어 a가 포인트 형을 반환하는 함수일 경우) b가 a보다 먼저 수행되는 것이 chain rule에 의해 실제로 보장되고 있습니다. 왜냐하면 b가 평가되지 않은 상태에서 a에 대입되는 것은 위험하고 비상식적인 행동이기 때문입니다. 이 경우는 예외적으로 평가 순서가 명시된 경우라고 볼 수 있겠지요.

그렇지 않습니다. a가 포인터를 반환한다 하더라도 반환값 자체는 임시 개체이므로
lvalue가 될 수 없고 따라서 =의 좌변에 올 수 없습니다. 이런 것은 가능하겠죠.
int* f(void);
int* g(void);

*f() = *g();

하지만 =의 피연산자의 평가 순서는 정해져 있지 않으므로 이 경우에도 g()가 f()보다
먼저 호출된다는 보장은 없습니다.

i = i +1에서 i의 평가 순서는 당연합니다. i + 1의 평가가 먼저 이루어지는 것이 지극히 당연하죠. 표준에는 이런 사항이 명기되어 있지 않지만 표준화 위원들도 이러한 속성을 chain rule이라고 부르며 당연히 여깁니다. (그래서 실제로 보장된다는 표현을 썼습니다.)

f()가 포인트 형을 반환하고 *를 통해서 locator-value을 얻어내는 아래의 코드의 경우에도

*f() = g();

어느 쪽이 먼저 평가되는지는 i = i + 1 문제처럼 chain rule을 따릅니다.

익명 사용자의 이미지

Quote:

코드:
*f() = g();

어느 쪽이 먼저 평가되는지는 i = i + 1 문제처럼 chain rule을 따릅니다.

아닙니다. f()와 g() 중에 어떤 것이 먼저 평가되는지는 정해져 있지 않습니다. A=B라는 수식이 있을때, A의 위치를 먼저 계산할 수도 있고 B의 결과값을 먼저 계산할 수도 있습니다.

익명 사용자의 이미지

http://groups.google.com/group/han.comp.lang.c/browse_thread/thread/e951fdecb63a86f0/d436ff925978e1f2?q=chain+rule&rnum=1&hl=ko#d436ff925978e1f2

chain rule은 f()와 g()의 평가순서에 대해 적용하는 것이 아니라, f()와 대입연산에 대해 적용하는 것입니다. 반드시 f()와 g()가 먼저 평가되고, 그 후에 =의 side effect인 '좌변의 결과값이 가리키는 위치에 우변의 결과값을 저장하는 행위'가 이루어진다는 뜻이지요. 좌변과 우변의 평가순서 사이에는 해당 법칙이 적용되지 않습니다.

죠커의 이미지

Anonymous wrote:
http://groups.google.com/group/han.comp.lang.c/browse_thread/thread/e951fdecb63a86f0/d436ff925978e1f2?q=chain+rule&rnum=1&hl=ko#d436ff925978e1f2

chain rule은 f()와 g()의 평가순서에 대해 적용하는 것이 아니라, f()와 대입연산에 대해 적용하는 것입니다. 반드시 f()와 g()가 먼저 평가되고, 그 후에 =의 side effect인 '좌변의 결과값이 가리키는 위치에 우변의 결과값을 저장하는 행위'가 이루어진다는 뜻이지요. 좌변과 우변의 평가순서 사이에는 해당 법칙이 적용되지 않습니다.

이제 알겠습니다. chain rule에 대해서 오해를 하고 있었군요.
*f() = g();
에서 g()가 호출된 후에 대입이 보장된다는 것이지 f()와 g() 중 어느 것이 먼저 호출되느냐를 보장해주는 것이 아니였군요.

잘못된 개념을 고쳐준 익명의 손님 두 분과 전웅씨에게 감사를 표합니다. 그리고 그 계기가 된 doldori님에도 감사합니다.

익명 사용자의 이미지

그나저나 a[a[i]] = 1; 이 정의되지 않는 행동을 갖는다는게 이해가 안되는군요. 이유가 뭘까요? 배열의 참조연산 자체는 아무런 side effect를 가지지 않을 텐데요.

정태영의 이미지

이 쓰레드를 보고 있으니... "해커, 그 광기의 역사" 와... "iCon" 에서 나온 스티브 워즈니악 이야기가 생각나는군요... 조금 인용해보겠습니다...

iCon 중에서 wrote:
워즈는 늘 그렇듯 터무니없이 적은 개수의 칩으로 브레이크아웃을 설계했다.
.
.
나중에 앨 앨콘은 문제가 있다는 걸 발견했다. "우리는 설계 자체를 이해할 수 없었다. 스티브도 디자인을 알지 못했고, 또 자기가 하지 않았다는 것을 회사가 눈치 채지 못하도록 했다. 그래서 우리는 그 게임을 다시 설계해야 했다.

오랫동안 꿈을 그리는 사람은 그 꿈을 닮아간다...

http://mytears.org ~(~_~)~
나 한줄기 바람처럼..

jo1413의 이미지

헛;; 댓글이 많이 달려있네요.
솔직히 이해 못하는게 많네요^^;;

책을 다시 한 번 정독해 봐야겠네요 ㅎㅎ
The practice of programming 한글판은 없나요?
인포북에서 나왔던데...번역은 괜찮은지요?
제가 영어에 약해서;;

------------------------------------------------------------
Get busy living, or get busy dying.

addnull의 이미지

정태영 wrote:
이 쓰레드를 보고 있으니... "해커, 그 광기의 역사" 와... "iCon" 에서 나온 스티브 워즈니악 이야기가 생각나는군요...

약간은 핀트가 어긋난것 같으면서도 동감이 가네요 ㅋㅋ
(터무니 없이 적은 칩으로 최적화라..)

sequence point나 chain rule로 인한 side effect를 모두 따져가면서 정밀하게 코딩하기 보다는
차라리 조금 효율이 떨어지고 코드가 길어져도
side effect의 가능성 자체가 없는 코드를 추구하는게 좋겠네요.

jo1413 wrote:
헛;; 댓글이 많이 달려있네요.
솔직히 이해 못하는게 많네요^^;;
책을 다시 한 번 정독해 봐야겠네요 ㅎㅎ

정독.. 솔직히 전 언어 공부하면서 한책을 정독한 적이 없군요.. ;;
아마도 첨으로 잡았던 C언어 책이 "가남사 터보씨정복"이었기 때문일듯..
그래서 구멍이 많은가 봅니다.
틈틈히 구멍을 메꿔야죠 ^^

이 쓰레드 덕분에 또하나의 구멍을 메꿔갑니다.

2005년 10월 28일.

죠커의 이미지

어니스트 wrote:
정태영 wrote:
이 쓰레드를 보고 있으니... "해커, 그 광기의 역사" 와... "iCon" 에서 나온 스티브 워즈니악 이야기가 생각나는군요...

약간은 핀트가 어긋난것 같으면서도 동감이 가네요 ㅋㅋ
(터무니 없이 적은 칩으로 최적화라..)

sequence point나 chain rule로 인한 side effect를 모두 따져가면서 정밀하게 코딩하기 보다는
차라리 조금 효율이 떨어지고 코드가 길어져도
side effect의 가능성 자체가 없는 코드를 추구하는게 좋겠네요.

오해가능성 없는 코드에 대해서는 공감을 하지만... 스티브 워즈니악 이야기에 대해서는 공감하지 않습니다. 어려운 코드도 필요한 법이고 그것을 이해할려는 노력도 중요하다고 생각합니다.

위에 전웅씨가 언급했던 a[a[i]] 코드의 진실이 무엇인지 궁금합니다.

addnull의 이미지

CN wrote:
위에 전웅씨가 언급했던 a[a[i]] 코드의 진실이 무엇인지 궁금합니다.

Quote:
다소 어려운 이야기를 하나 꺼내자면 a[i] = a[a[i]]; 같은 수식은 위에서 설명한 chain rule (과 공식적인 표준의 규칙) 에 의해 정의된 행동을 보장받지만,
a[a[i]] = 1; 와 같은 수식은 표준의 명시적인 규칙을 적용할 경우 sequence point 규칙에 의해 정의되지 않는 행동을 갖는다는 것입니다.

저도 이 부분이 이해가 안가는군요..?

2005년 10월 29일.

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.