|
Visual C++ 위자드로 자동 생성되는 코드들 중에서 stdafx.h 와 stdafx.cpp 이 있다. 여기에서 stdafx 란 Standard Application Freamworks 의 약자로 개발자들의 생산성 향상을 위해 MS 에서 제공하는 소프트웨어 라이브러리 체계를 뜻하며, MFC 로 구성되어 있다. 참고로 많이 사용되는 application framework 로는 .NET Framework( Windows 계열 ), Cocoa ( Objective C / Mac OS X), Swing (Java) 등이 있다. 그럼 Precompiled header (미리 컴파일된 헤더. 여기에서는 precompiled header 로 통일) 란 무엇인가... C / C++ 언어에서 헤더 파일은 C 전처리기(preprocessor) 에 의해 자동적으로 소스 코드를 포함하게 된다. 그런데 일부 헤더 파일의 경우 방대한 크기의 소스 코드를 포함할 수 있고( 예를 들면 window.h), 이런 코드들을 매번 컴파일하면 컴파일 시간이 매우 길어지게 된다. 그래서 자주 바뀌지 않는 기본적인 라이브러리들의 경우에 컴파일 시간을 줄이고자 컴파일러가 사전에 헤더 파일들을 미리 컴파일 해 놓고 쓸 수 있게 하고 있다. 이렇게 컴파일 시간을 줄이기 위해 사전에 컴파일한 결과물이 VC 의 경우 pch(precompiled header) 라는 확장자 명으로 저장된다. 비주얼 스튜디어의 솔루션 폴더에 생기는 프로젝트명.pch 가 바로 그것이다. precompiled header 를 사용할 경우 precompiled header 로 지정한 헤더 파일 및 소스 코드는 컴파일시에 컴파일 되지 않고 pch 의 결과물을 가져다 사용하게 되는 것이다. 자.. 이제 원리를 이해했으니 많은 궁금중들이 풀릴 수 있다. 질문 1 ) stdafx.h 와 stdafx.cpp 파일의 용도가 무엇인가요? stdafx.h 가 포함하는 정보는 무엇들인가요? 앞서 말했듯이 MFC 에서 자주 사용되는 공용 소스들을 precompiled header 로 만들어 제공하기 위해 디폴트로 stdafx.h 와 sfdafx.cpp 이 위자드에서 자동 생성되는 것이다. 참고로 precompiled header 는 stdafx.h 외에 다른 파일들을 설정할 수도 있다. (아래 그림과 같이..) VC 에서는 프로젝트 속성 - C/C++ - 미리 컴파일된 헤더 항목에서 Precompiled header 사용 여부를 선택할 수 있다. stdafx.h 에 포함하는 내용들을 살펴보면 윈도우 객체 생성에 필요한 기본 클래스 (afx.h / afxwin.h 등), 윈도우 컨트롤( afxctl.h / afxcmn.h 등), 기본 DB 관련 클래스 ( afxdb.h / afxdao.h ), 네트워크 관련 클래스 ( afxsock.h ) 등등 기본적인 프레임워크 구축에 필요한 필수 헤더들이 포함되어 있다. 질문 2 ) 다음의 에러들은 무엇인가요 에러 : fatal error C1010: unexpected end of file while looking for precompiled header directive 미리 컴파일된 헤더 지시문을 찾는 동안 예기치 않은 파일의 끝이 나타났습니다 ( 혹은 버전에 따라 "Did you forget to add '#include "stdafx.h"' to your source?" 라는 문구가 표시되기도 함 ) 해결방안 : precompiled header 를 사용하도록 설정된 상태에서 컴파일을 하였는데 precompiled header 를 찾지 못했다는 의미. 보통 해당 소스코드에 stdafx.h 가 인클루드 되어 있지 않거나, stdafx.h 의 위치가 잘못 되어 있어서 발생한다. stdafx.h 를 소스코드의 가장 위쪽에서 include 하도록 해 주면 된다. 혹은 precompiled header 가 잘못 세팅되어 찾지 못하는 문제일수도 있다. precompiled header 를 사용 안함으로 체크하여 pch 를 현재 소스 코드에 맞게 재 생성을 해 준다. 에러 : fatal error C1853: "Debug/test.pch" is not a precompiled header file created with this compiler 해결방안 : 현재 소스 코드에서 사용할 수 있는 pch 와 버전이 맞지 않는다는 의미. precompiled header 를 사용 안함으로 체크하여 pch 를 현재 소스 코드에 맞게 재 생성을 해 준다. 참고로, precompiled header 를 사용 안함으로 체크할 경우 매번 전체 헤더파일을 재빌드 하므로, 아래와 같이 "미리 컴파일된 헤더 만들기"를 선택하여 최초에 한번 pch 를 만들어 주면, 그 다음부터는 precompiled header 사용하기를 선택하여 사용할 수 있다. 질문 3 ) 그렇다면 stdafx.h 에 어떤 내용을 넣으면 되나요 앞서 말했듯이 stdafx.h 는 한번만 미리 빌드해 놓고 그 다음부터는 재빌드 안하고 쓰는 모듈들이 되야 하므로, precompiled header 인 stdafx.h 에는프로젝트 진행 중에 거의 값이 바뀌지 않는 외부 라이브러리나 전역 변수 등의 내용을 포함시키면 된다. 이들을 pch 로 만들어 놓고 빌드하면 매번 재빌드 하지 않고 컴파일 시간을 많이 줄일 수 있다. 참고로, GCC ( .gch ) , C++ Builder ( vcl.h ) 등에서도 precompiled header 를 지원한다. 참고 : Wikipedia - Precompiled header precompiled header 를 씁시다 (제발) 'IT Story > Programming Language' 카테고리의 다른 글
|
BLOG ARTICLE IT Story/Programming Language | 11 ARTICLE FOUND
- 2010/04/28 Precompiled header 에 대해서 ( stdafx.h 와 stdafx.cpp )
- 2009/11/02 C++ 과 C# 의 차이 (6)
- 2009/05/18 Visual Studio LNK 4222 경고 - exported symbol 'symbol' should not be assigned an ordinal
- 2009/03/25 Visual Studio 에서 재미있는 delete 현상 (4)
- 2009/03/18 디버그 사례 - Visual Studio 6.0 에서 close() 함수 사용시 (2)
- 2009/02/09 fwrite 와 fprintf 의 차이점 (8)
- 2008/12/01 goto Statement Consider Harmful
- 2008/10/24 비트 이동 연산자 ( << , >> ) 의 결과
- 2008/07/13 C++ 과 C# 의 차이... (4)
- 2007/09/30 int main() vs void main() (7)
- 2007/09/12 프로그래밍 언어에서 음수의 나눗셈 처리 (12)
|
앞으로 공적으로는 C++ 을 쓰고, 사적으로는 C# 으로 쓰기로 마음먹었다. C# 자체도 좋은 언어이지만, WCF, WPF, Silverlight, ASP.Net 사용도 고려해 볼때 꼭 익혀놓아야 하는 언어임은 틀림없다. C# 을 쓰면서 느끼는 C++ 과의 차이점들을 정리해 놓고자 한다. 1. Windows Application 개발 환경의 차이. C# 의 개발 환경은 전통적인 Visual C++ 보다는 Visual Basic 에 가깝다. 폼을 디자인하고, 이 폼을 동작하는 코드를 별도로 코딩하는 환경은 보다 RAD 를 지향하고 개발생산성을 중시한 C# 의 철학을 잘 보여준다. 또한 윈도우즈 어플 개발시 Visual C++ 에서 나눠져 있던 SDI, MDI, Dialog Based 구분이 없어지고 C# 에서는 Windows Forms 이라는 하나의 형태로만 존재한다. ( 도구상자에 MDI Parent 라는 컨트롤이 존재하는 것은 확인했다. ) 2. 보다 강력해진 Intellisense Visual C# 의 Intellisense 는 정말 강력하다. 예약어로 정의된 키워드들과, 라이브러리에 이미 정의된 멤버들이 팝업으로 표시되는 것은 물론이고, 심지어 개발자가 정의한 변수/함수 들도 인텔리센스에서 나타난다. 이걸 처음에 보고 얼마나 감동받았는지 ㅜ.ㅜ 덕분에 코딩 시간이 많이 단축된다. 여담이지만, TopCoder 를 할 때 C++ 개발자들은 조금이라도 코딩을 빨리 하기 위해서 자주 사용하는 긴 문장들은 매크로를 즐겨 쓴다. 예 ) #define FOR(i,a,b) for(int i = (a); i < (b); ++i) #define REP(i,n) FOR(i,0,n) 그런데 C# 에서는 굳이 저릴 필요가 없다. 인텔리센스가 잘 받쳐주고 있으니까... 사실, 인텔리센스는 언어의 기능이 아니라 툴의 기능이므로, 이런 강력한 인텔리 센스는 Visual C++ 에도 충분히 도입할 수 있다고 보여지는데... C# 과 C++ 에 차이를 둔 것은 결국 정책적으로 MS 가 C# 을 밀고 있다는 이야기만 확인시켜주는 셈이다... 그리고 여담. 나는 ret 이란 변수를 즐겨 쓰는데 이걸 쓰다보면 인텔리센스가 return 으로 자꾸 인식해서 좀 불편하다. 3. C# 은 더욱 더 강력하게 객체지향적이다. 자료구조등을 새로 선언할때 class 형으로 만들어서 써야 한다. 4. 자료 구조의 차이 C++ STL 의 List, Vector 대신에 C# 에서는 ArrayList 가 제공된다. C++ STL 의 Map 대신에 C# 에서는 Hashtable 이 제공된다. C++ STL 의 Pair 대신에 C# 에서는 KeyValuePair 가 제공된다. C++ 의 경우 STL 에서 제공하는 자료구조들을 쓰기 위해서 C# 에서는 Collection 을 인클루드 한다. 5. 형변환이 보다 엄격하면서도, 쉬워졌다. 예를 들어 C++ 에서 허용되는 다음과 같은 코드가 C# 에서는 에러가 난다. 반면에 데이터 타입의 변환은 Convert 라는 객체를 통해서 ToString, ToInt16, ToInt32, ToDateTime 과 같이 다양한 형태의 형변환을 메소드로 지원해서 형변환이 아주 쉬워진다. 6. 클래스 멤버들의 public, private, protected 구분이 더 엄격해졌다. C++ 과 달리 C# 에서는 메소드, 변수 마다 붙인다. struct 의 경우 C++ 에서는 별도로 선언을 하지 않아도 암시적으로 모든 멤버가 public 으로 선언된다. 하지만 C# 에서는 struct 를 사용할때 위와 같이 멤버들에 대해서 명시적으로 public 인지 지정해야 한다. 7. C# 은 가비지 콜렉션을 지원하며 C++ 과 달리 new 는 존재하지만 delete 는 없다. C# 에서 객체를 생성할때는 항상 new 로 생성한다. 하지만 C++ 과 달리 delete 를 해줘야 하는 것이 아니라, 가비지 콜렉터가 자동으로 매니지드 힙의 메모리 영역에서 생성된 객체의 메모리를 지워준다. 예를 들어 C# 에서 배열을 선언할 때 아래와 같이 쓴다. int [] dat = new int []; 2차원 배열은 아래와 같이 할당한다. int [,] dat = new int [100,100]; // 다중 배열에서 콤마를 쓰는 이런 방식이 처음엔 상당히 생소했다. new 로 생성하지만 delete 를 하지는 않는다. 명시적으로 프로그래머가 힙 영역에서 데이터를 삭제할 때는 Dispose 명령을 사용한다. 'IT Story > Programming Language' 카테고리의 다른 글
|
-
김훈동 2009/11/13 17:57 댓글주소 수정/삭제 댓글쓰기
2. 에 추가하여... 코드 스니펫 이라는 게 있는데.... 자주 쓰는 긴 코드의 코딩 패턴을 XML 로 정의 해놓고, 거기에 바뀌는 부분만을 tab 으로 옮겨가며 코딩 하는게 있어...
예를 들어 prop 라고 치고 tab 을 두번 눌러봐바.... 자바에서 멤버변수 선언하고 getter , setter 를 쫙 써준 다음에 그걸 하나의 DTO 클래스 혹은 ValueObjce 클래스로 만들어서 객체를 쓰던 방식을 .NET 에서는 프로퍼티 라는 걸로 깔끔하게 선언해 쓰는 것을 권장하고 있는데... prop 텝 2번 눌러서 멤버변수 영역에서 변수를 프로퍼티 로 정의하면 private 변수와 이를 public 으로 노출하는 getter 와 setter 가 한방에
코드 제너레이션이 가능해...
swich 구문이나 foreach 구문 등도 미리 예약된 코드 스니펫 단축명령어가 다 존재하고 혹은 내가 자주 사용하는 코딩 패턴을 코드 스니펫으로 custom 지정도 가능해.... 그리고 custom 코드 스니펫 만들때는 복잡하게 XML 을 일일이 짜줄 필요는 없고.... 코드 스니펫을 자동으로 만들어서 VS 에 연동시켜주는 Free 소프트웨어들이 인터넷에 많이 돌아다녀... 자주쓰는 DB 접속 구문이나 Try Catch 구문, Using 구문, 그리고 특히 프로젝트 표준 주석등을 쓸때 스니펫을 쓰면 딱이쥐... -
김훈동 2009/11/13 18:07 댓글주소 수정/삭제 댓글쓰기
4. 에 추가하여... .NET Generic 도 잘 배워봐...
STL 의 대부분의 기능이 구현되어 있고.. Java Generic 보다 훨씬 빠로고 안정적이야..
아래는 C++ 템플릿과 C# Generic 의 차이를 알려주는 MSDN 의 내용....
[generics 구현]
C++에서 템플릿은 사실 매크로일 뿐이며 컴파일된 이진수로 유지되지 않습니다. 특정 형식의 템플릿 클래스를 사용하지 않으면 컴파일러가 템플릿 코드를 컴파일할 수도 없습니다. 형식을 지정하면 컴파일러가 코드를 인라인에 삽입하여 generic 형식 매개 변수의 모든 항목을 지정된 형식으로 바꿉니다. 템플릿 클래스에서 발생한 컴파일 오류는 템플릿 클래스를 사용할 때만 발견할 수 있습니다. 또한 특정 형식을 사용할 때마다 컴파일러는 사용자가 이미 응용 프로그램의 다른 곳에서 템플릿 클래스에 대해 해당 형식을 지정했는지에 관계없이 형식별 코드를 삽입합니다. 따라서 코드가 비대해져 로드 시간이 길어질 뿐 아니라 메모리 공간도 많이 차지하게 됩니다.
.NET 2.0에서는 generics가 IL(Intermediate Language) 및 CLR 자체를 기본적으로 지원합니다. generic C# 서버 쪽 코드를 컴파일하면 컴파일러가 이를 다른 모든 형식과 마찬가지로 IL로 컴파일합니다. 그러나 IL에는 실제 특정 형식의 매개 변수나 자리 표시자만 들어 있습니다. 또한 generic 서버의 메타데이터에는 generic 정보가 들어 있습니다.
클라이언트 쪽 컴파일러는 해당 generic 메타데이터를 사용하여 형식의 안전성을 지원합니다. 클라이언트가 generic 형식 매개 변수 대신 특정 형식을 제공하는 경우 클라이언트의 컴파일러는 서버 메타데이터에 있는 generic 형식 매개 변수를 지정된 형식으로 대체합니다. 그러면 generics가 전혀 사용되지 않은 것처럼 클라이언트의 컴파일러에 서버의 형식별 정의가 제공됩니다. 이러한 방법으로 클라이언트 컴파일러는 올바른 메서드 매개 변수, 형식 안전성 검사 및 형식별 IntelliSense®도 적용할 수 있습니다.
흥미로운 점은 .NET이 서버의 generic IL을 기계어 코드로 컴파일하는 방식입니다. 사실, 실제로 만들어지는 기계어 코드는 지정된 형식이 값 형식인지 아니면 참조 형식인지에 따라 달라집니다. 클라이언트가 값 형식을 지정하면 JIT 컴파일러가 IL에 있는 generic 형식 매개 변수를 특정 값 형식으로 바꾸고 네이티브 코드로 컴파일합니다. 그러나 JIT 컴파일러는 이미 생성한 형식별 서버 코드를 추적합니다. 이미 기계어 코드로 컴파일한 값 형식을 사용하여 generic 서버를 컴파일하도록 JIT 컴파일러에 지정하는 경우 해당 서버 코드에 대한 참조만 반환됩니다. JIT 컴파일러는 차후의 모든 항목에서 동일한 값 형식별 서버 코드를 사용하므로 코드가 비대해지지 않습니다.
클라이언트가 참조 형식을 지정하면 JIT 컴파일러가 서버 IL에 있는 generic 매개 변수를 개체로 바꾸고 네이티브 코드로 컴파일합니다. 해당 코드는 차후의 모든 참조 형식 요청에서 generic 형식 매개 변수 대신 사용됩니다. 이러한 방법으로 JIT 컴파일러는 실제 코드만 다시 사용합니다. 인스턴스는 여전히 크기에 따라 관리 힙에 할당되며 캐스팅은 없습니다.
[generics 이점]
.NET에서는 generics를 구현할 때 사용한 코드와 작업을 generics를 사용할 때 다시 사용할 수 있습니다. 값 형식을 사용하거나 참조 형식을 사용하거나에 관계없이 코드를 비대하게 만들지 않고도 형식 및 내부 데이터를 변경할 수 있습니다. 코드를 한 번 개발하고 테스트하고 배포한 후에는 모든 컴파일러 지원과 형식 안전성이 보장되는 상태에서 미래의 형식을 포함한 모든 형식에 다시 사용할 수 있습니다. generic 코드를 사용하면 값 형식을 boxing 및 unboxing하거나 참조 형식을 다운 캐스팅하지 않아도 되므로 성능이 크게 향상됩니다. 값 형식을 사용하면 형식에 액세스할 때 일반적으로 성능이 200% 향상되며, 참조 형식을 사용하면 100%의 성능 향상을 기대할 수 있습니다. 물론 전체 응용 프로그램의 성능은 향상될 수도 있고 그렇지 않을 수도 있습니다. 이 기사에서 사용한 소스 코드에는 간단한 루프에서 스택을 실행하는 마이크로 벤치마크 응용 프로그램이 포함되어 있습니다. 이 응용 프로그램을 사용하면 개체 기반 스택과 generic 스택에서 값 형식 및 참조 형식을 사용해 볼 수 있으며 루프 반복의 수를 변경하여 generics가 성능에 미치는 영향을 확인할 수도 있습니다. -
김훈동 2009/11/13 18:13 댓글주소 수정/삭제 댓글쓰기
7. 에 추가하여...
.NET 은 기본적으로 명시적인 delete 는 없는데....
프로젝트를 하다보면 그시점에 명시적으로 delete 를 하고싶을때가 분명 있거든... 그때 쓰는게... using 구문이쥐...
네임스페이스 지정하는 using 문 말고... 아래 같은거 할때 쓰는 using...
파일오픈 객체 소멸을 가비지 컬렉터한테 넘기는 우는 처음 시작하는 .NET 개발자가 자주 범 하는 실수 중 하나쥐...
using (System.IO.StreamReader sr = new System.IO.StreamReader(@"C:\Users\Public\Documents\test.txt"
)
{
string s = null;
while((s = sr.ReadLine()) != null)
{
Console.WriteLine(s);
}
}
Visual Studio LNK 4222 경고 - exported symbol 'symbol' should not be assigned an ordinal
IT Story/Programming Language 2009/05/18 11:22|
LNK4222 경고는 주로 Visual Studio 6.0 에서 개발한 dll 을 2003 이상에서 빌드할 때 발생한다. 6.0 에서는 보통 dll 의 def 파일을 다음과 같이 생성한다. 예를 들어 dll 의 TEST.def 파일이 아래와 같이 되어있다면 DllCanUnloadNow @1 PRIVATE DllGetClassObject @2 PRIVATE DllRegisterServer @3 PRIVATE DllUnregisterServer @4 PRIVATE Visual Studio 2003 이상의 컴파일러는 다음과 같은 경고를 발생시킨다. 1>.\TEST.def : warning LNK4222: 내보낸 'DllCanUnloadNow' 기호를 서수로 지정하면 안 됩니다. 1>.\TEST.def : warning LNK4222: 내보낸 'DllGetClassObject' 기호를 서수로 지정하면 안 됩니다. 1>.\TEST.def : warning LNK4222: 내보낸 'DllRegisterServer' 기호를 서수로 지정하면 안 됩니다. 1>.\TEST.def : warning LNK4222: 내보낸 'DllUnregisterServer' 기호를 서수로 지정하면 안 됩니다. ( Visual Studio 영문 버전의 경고 문구는 아래와 같다. ) warning LNK4222: exported symbol 'DllRegisterServer' should not be assigned an ordinal http://msdn.microsoft.com/ko-kr/library/8e705t74(VS.80).aspx 위의 MSDN 을 읽어보면 도움을 얻을 수 있는데, 결과적으로 얘기하자면 6.0 에서 쓰던 것과 같은 서수 표기방식은 아래 함수들에 대해서는 사용하면 안된다. ( 이유 : 기호를 서수로 표기할 경우 실제 사용할 주소 테이블보다 큰 슬롯을 사용할 수 있음 ) 그러므로 아래 함수들에 대해서는 서수 표기방식을 사용해서는 안된다. DllCanUnloadNow DllGetClassObject DllGetClassFactoryFromClassString DllInstall DllRegisterServer DllRegisterServerEx DllUnregisterServer 위의 def 파일 코드는 아래와 같이 고쳐주면 깔끔하게 해결이 된다. DllCanUnloadNow PRIVATE DllGetClassObject PRIVATE DllRegisterServer PRIVATE DllUnregisterServer PRIVATE 'IT Story > Programming Language' 카테고리의 다른 글
|
|
이런 코드가 release 모드에서 정상적으로 실행되는 것도 이상하다. 하다못해 컴파일러가 warning 이라도 띄워주는 것이 맞지 않을까 싶다. 'IT Story > Programming Language' 카테고리의 다른 글
|
|
남이 짜놓은 VS 6 프로젝트를 VS 2005 로 포팅중 다음과 같은 디버그 에러를 발견... 실제로 close.c 함수를 찾아서 따라가 보면 close.c 함수의 47 번째 라인은 아래와 같다. 즉, file 등을 사용한 후에 닫을 때 fd = close() 처럼 리턴 결과를 fd 로 넘기는데, open 에 실패하여 fd 값이 음수인 경우 정상적으로 close 를 시킬 수 없으므로 위와 같이 Assertion Failed 를 내게 되어 있다. 예를 들어서 아래와 같은 코드에서 assertion failed 가 발생할 수 있다. 위의 경우 파일 열기에 실패하여 fd 값이 음수로 오는 경우 6 라인에서 open 도 안했는데 close() 를 호출한 것이 되어 close 함수가 정의되어 있는 close.c 함수에서 Assertion Failed 가 된다. 이런 경우, open 에 성공했을 때만 close 해줘야 하므로 close 함수는 4 라인에 위치해야 한다. 결국, Call Stack 을 열심히 뒤져본 끝에 위와 같이 close() 함수를 잘못 호출하고 있는 부분을 찾은 끝에 디버깅에 성공했다. 재미있는 것은 위와 같은 코드가 VS 6.0 에서는 정상적으로 빌드가 되고 실행이 된다는 것. 이와 같은 잠재적인 버그도 검출하지 못하는 VS 6.0 은 역시 하루빨리 버려야 한다 -0- 참고로 소켓 통신등을 구현할때에도 VS 6.0 에서 문제없이 쓰던 close( sock ) 함수는 closesocket( sock ) 와 같이 closesocket 함수로 바꿔 써줘야 한다. 'IT Story > Programming Language' 카테고리의 다른 글
|
|
fwrite 와 fprint 의 차이점은? 위의 질문은 친구가 넥슨에 면접을 보러 갔을때 나온 질문이었다고 한다. 술자리에서 잠시 나온 이야기였지만, 흥미가 생겨서 지금 찾아보니 잘 정리된 글이 있다. fwrite() & fprintf() -- binary or text?? 우선, 저수준함수인 fwrite 를 오버라이딩해서 파라미터에 따른 서식에 맞게 보다 편리하게 출력할 수 있도록 만든 것이 fprintf 이다. 위의 링크 글에 설명되었듯이, fwrite 는 버퍼에 있는 내용을 그대로 다 출력한다. 반면에 fprintf 는 표준출력 (Standard output stream) 모드로 동작하여 일반적인 문자열 버퍼 출력 방식을 따르게 된다. 즉 '/0' 과 같은 문자열 종료 캐릭터를 만나면 출력을 종료한다. 아래 예제를 보면 명확하다. fprintf 를 사용하여 출력하는 경우 HELLO 까지만 찍고, 문자열 종료 널문자 '\0' 을 만났기 떄문에 출력을 종료한다. 반면에 fwrite 는 '\0' 와 상관없이 버퍼 전체의 내용을 그대로 출력해서 HELLO\0WORLD 를 모두 출력한다. ( 텍스트 파일에서는 HELLO WORLD 로 보임 ) 결국 printf 서식에 맞게 문자열 출력방식으로 출력하고 싶으면 fprintf 를 쓰고, 버퍼에 있는 모든 내용 혹은 버퍼내의 특정 범위의 내용을 그대로 출력하고자 할 때는 fwrite 를 쓰면 된다. 'IT Story > Programming Language' 카테고리의 다른 글
|
|
많은 프로그래밍 언어에서 사용하고 있는 goto 는 지정된 label 의 statement 로 바로 점프할 수 있는 강력한 키워드이다. 이른바, goto 의 사용은 코드의 가독성이 떨어지며 유지보수가 어려운 스파게티 코드(spaghetti code) 를 생산한다고 하여, 많은 논란이 되어왔다. ( 최근에 사용되는 몇몇 고수준 언어에서는 goto를 지원하지 않는다. 예를 들면 Java 의 경우 goto 문이 예약어로 지정되어 있으나 명령어로 존재하지는 않는다. ) 1960-70 년대에, 많은 컴퓨터과학자들은 loop 와 if-else 와 같은 구조적인 flow-control 을 활용하여 goto 를 대체할 수 있다고 결론지었다. 하지만 예외처리를 하기위한 경우나 중첩 루프문 안에서 goto 의 사용은 유용하다는 주장도 있다. 1968년, 다익스트라 (Edsger Dijkstra) 는 "Go To Statement Consider Harmful" 이라는 글을 통해 goto 를 사용함에 따라 프로그래밍 언어의 분석이 복잡해지고 무결성 검증이 어려워진다며 goto 의 사용을 비판했다. 이 글의 핵심 요지는 인간의 인지 능력은 코드를 읽을 때 순차적으로 시간의 흐름에 따라 라인을 읽어가는 것이 자연스러운데, goto 사용을 통해서 라인을 이리저리 건너뛰는 코드는 이해하기 매우 난해하며 유지, 관리도 어려워 진다는 점을 얘기하고 있다. ( 이 논문은 벌써 40 년이나 된 논문인데... 이 논문으로 시작된 goto 문 안쓰기 운동은 여전히 유효한 것 같다. ) Edsger Dijkstra. 척 보기에도 guru 같이 생겼다. 반면에, Donald Knuth 는 "structured Programming with go to Statement" 라는 글을 통해 구조적인 프로그래밍 언어에 있어서 goto 를 사용해서 원하는 구조를 구축할 수 있다고 분석했다. ( 이상 Wikipedia 에서 발췌 ) 무분별한 goto 의 사용은 제한해야 겠지만, 필요한 부분에서 적절하게 사용하는 경우 코딩이 보다 효율적이 될 수 있을 것 같다. 리눅스 커널에서도 goto 는 사용되고 있다고 하니... 특히, 다중 루프 내에서 한번에 탈출하고자 할 경우에 매우 유용하다. ( 함수 안이라면 return 을 써도 되겠지만 피치 못하게 main 내에서 사용되는 경우들도 많으니... 예를 들면 알고리즘 문제풀이를 할때 goto 를 종종 썼던 것 같다. ) 누군가 다른 사람이 짜 놓은 코드를 읽어보다가... 예외처리를 하기 위해서 goto 를 쓰는 대신에 아래와 같이 써놓은 것을 봤다. 저렇게 해서 필요시 break 를 써서 한번에 루프를 탈출하여 예외처리를 하는 코드였다. 원래 continue 나 break 가 일부 goto 와 유사한 기능을 하도록 고안되었다 하니 목적에 맞기는 한데, 저렇게 쓸 경우에 예외상황이 다중 루프의 내에 있을 경우에는 여전히 한번의 break 만으로는 탈출이 불가능하다. 암튼 저런 코드를 보다보니 생각나서 자료를 좀 뒤져여 본다.. 관련 글 Dijkstra 가 goto 에 시비건 진짜 이유는 1 ( 마소에 연재된 내용인데, 상당히 재미있는 기사다... ) Dijkstra 가 goto 에 시비건 진짜 이유는 2 'IT Story > Programming Language' 카테고리의 다른 글
|
|
재미있는 글을 보고서.. 나도 포스팅 알다시피, 비트연산자 << n 은 n 비트 만큼 왼쪽으로, >> n 은 n 비트만큼 오른쪽으로 비트를 이동(shift) 한다는 의미이다. 예를 들어, int n = 1; n =<< 1; 위와 같이 하면 n 의 값은 1 비트씩 왼쪽으로 밀려서 답이 2 가 된다. ( 일반적으로 2^n 값을 곱하거나 나눌 때, 비트 연산자를 종종 활용한다. 비트연산의 장점은 비트 단위에서 직접 조작하므로 일반 사칙연산보다 더 빠르다는 것.. 단, 이러한 연산법은 양수일 경우에만 제대로 동작한다. 자세한 내용은 아래 다시 설명 ) 그러다면 아래와 같은 비트 연산의 결과는 어떻게 될까? ( 테스트 환경은 Intel 계열 CPU ) Quiz 1) int n = 1; n =<< 31; cout << n << endl // n 은 얼마?? Quiz 2) int n = 1; n =<< 32; cout << n << endl // n 은 얼마?? Quiz 3) int n = 1; n =<< 33; cout << n << endl // n 은 얼마?? Quiz 4) int n = -2; n =<< 31; cout << n << endl // n 은 얼마?? Quiz 5) int n = -256; n =>> 31; cout << n << endl // n 은 얼마?? Quiz 6) int n = -256; n =>> 33; cout << n << endl // n 은 얼마?? Quiz 7) int n = 1; n <<= -123; cout << n << endl // n 은 얼마?? C99 표준문서 의 6.5.7 Bitwise shift operators 를 보면 다음과 같이 정의되어 있다. If the value of the right operand is negative or is greater than or equal to the width of the promoted left operand, the behavior is undefined. The result of E1 << E2 is E1 left-shifted E2 bit positions; vacated bits are filled with zeros. If E1 has an unsigned type, the value of the result is E1 * 2^E2, reduced modulo one more than the maximum value representable in the result type. If E1 has a signed type and nonnegative value, and E1 * 2^E2 is representable in the result type, then that is the resulting value; otherwise, the behavior is undefined. The result of E1 >> E2 is E1 right-shifted E2 bit positions. If E1 has an unsigned type or if E1 has a signed type and a nonnegative value, the value of the result is the integral part of the quotient of E1 / 2^E2. If E1 has a signed type and a negative value, the resulting value is implementation-defined. 우측 피연산자 값이 음수이거나 좌측 피연산자의 범위보다 큰 경우의 행위는 정의되지 않았다 (undefined) E1 이 양수인 경우에는 E1 << E2 는 E1 을 E2 비트만큼 왼쪽으로 이동, 이 것은 E1 에 2^E2 을 곱한 것과 같다. E1 이 음수인 경우의 동작은 정의되지 않았다. E1 이 양수인 경우 E1 >> E2 역시 E1 을 E2 비트만큼 오른쪽으로 이동, 이것은 E2 를 2^E2 를 나눈 것과 같다. 그러나, E1 이 음수인 경우, 결과는 구현에 따라 달라진다 (implementation-defined) 결국, C99 표준에 정의되지 않은 사항은 CPU 명령어 정의에 따라 달라지게 되는데... Intel CPU 에서의 동작은 다음과 같다. 우선, << , >> 연산자의 오른쪽 피연산자는 short 일때 4 자리(0 ~ 15까지), int 일때 5 자리(0 ~ 31 까지), 64 비트형인 long long 일때 6 자리 (0 ~ 63 까지) 의 범위에 대해서만 유효하다. 다시 말하면 오른쪽 피연산자는 short 일때 하위 4 비트만 사용하며, int 일때 하위 5 비트만 사용하며, long long 일때 하위 6 비트만 사용한다. 쉬프트 연산은 왼쪽 피연산자 값의 범위 (비트 길이) 내에서만큼만 쉬프트 연산이 이루어진다. 즉, Quiz 2 의 n <<= 32 에서 32 는 100000 이 되고, 하위 5 비트는 00000 이므로 n <<= 32 는 n <<= 0 과 같다. Quiz 3 의 n <<= 33 는 33 이 100001 이며 이중 하위 5 비트는 00001 이므로 n <<= 33 은 n <<= 1 과 같아지는 것이다. n << x 이면 일반적으로는 n 이 2^x 만큼 곱한 것과 같아지게 되는데, Quiz 1 의 경우는 1 << 31 의 답이 1 * 2^31 이 아닌 -2147483648 ( -1 * 2^31 ) 이 된다. 이렇게 되는 이유는 int 의 경우 최상위 비트가 부호 비트가 되어 부호 비트가 0 에서 1 로 바뀌면서 음수값을 취하게 되기 때문이다. ( 최상위비트 참고 ) Quiz 1 의 결과 00000000 00000000 00000000 00000001 // 1 을 10000000 00000000 00000000 00000000 // 31 만큼 좌측을 이동, 부호 비트가 1 이 되며 답이 -2147483648 만약 n 이 unsigned int 였다면, 답은 양의 정수 2147483648 이 된다. Quiz 2 의 결과 00000000 00000000 00000000 00000001 // 1 을 00000000 00000000 00000000 00000001 // <<= 32 는 <<= 0 과 같으므로, 결과는 변함 없어 답은 1 Quiz 3 의 결과 00000000 00000000 00000000 00000001 // 1 을 00000000 00000000 00000000 00000010 // <<= 33 는 <<= 1 과 같으므로, 결과는 1 비트 좌측이동하여 답은 2 Quiz 4 의 결과 11111111 11111111 11111111 11111110 // -2 을 00000000 00000000 00000000 00000000 // <<= 31 로 31 만큼 좌측 이동. 결과는 0 이 된다. 왼쪽 피연산자가 음수인 경우에 << 의 경우는 왼쪽으로 비트를 이동하면서 0 으로 채운다. 그런데, implementation-defined 되 있는 >> 연산자는 동작방식이 다르다. << 연산자의 경우 양수/음수 모두 이동한 자리를 0 으로 채운다. 반면에 >> 연산자의 경우 양수일 경우에는 오른쪽으로 비트를 이동하면서 이동한 자리는 0 으로 채우는데, 음수일 경우에는 이동한 자리를 1 로 채우게 된다. 그래서 Quiz 5 의 경우 오른쪽으로 이동하고 난 후의 남은 31개 비트를 모두 1 로 채우므로 답은 -1 이 된다. Quiz 5 의 결과 11111111 11111111 11111111 00000000 // -256 11111111 11111111 11111111 11111111 // 31 만큼 우측 이동, 음수의 경우 이동한 자리는 1 로 채워짐. 답은 -1 Quiz 6 의 결과 11111111 11111111 11111111 00000000 // -256 11111111 11111111 11111111 10000000 // 33 만큼 우측이동, >> 33 은 >> 1 과 같아서 답은 -128 >> 연산자 역시 << 연산자와 마찬가지로 int 의 경우 오른족 피연산자의 하위 5 비트에 해당하는 값만큼만 이동을 한다. 그러므로 int n 의 경우 n =>> 33 은 n=>> 1 과 같아진다. 음수일 경우 >> 연산을 하여 이동한 자리를 0 이 아닌 1 로 채우는 이유는 아마도 n =-2, n >>= 1 일때 n = -1 이 되는 것과 같이 음수인 경우 나눗셈 연산이 정상적으로 동작하도록 하게 하려는 의도였을 것이다. 하지만 음수는 2 의 보수로 계산되기 때문에 음수인 경우에는 n 비트 쉬프트 연산의 결과가 2^n 곱셉/나눗셈의 결과와는 동일하게 동작하지 않는 경우도 종종 생긴다. ( 2^n 꼴의 수가 아닌 경우 이러한 경우가 발생 ) 예를 들면 n = -10 일때 n =>> 3; 의 결과는 n /= 8; 과는 다른 결과가 나온다. ( n =>> 3 이면 n = -2, n /= 8 이면 n = -1 ) 11111111 11111111 11111111 11110110 // -10 11111111 11111111 11111111 11111110 // 우측으로 3 비트 이동한 후의 결과, -2 가 됨 끝으로, Quiz 7 과 같이 쉬프트 연산에 있어서 오른쪽 피연산자의 값이 음수일 경우 역시 오른쪽 피연산자의 하위 5 개 비트의 값에 해당하는 크기만큼만 비트 이동을 하므로, -123 이 11111111 11111111 11111111 10000101 이며 이중 하위 5 비트는 00101 이므로 결국 n <<= -123 은 n <<= 5 와 같다. 그러므로 1 <<= 5 한 결과는 32. 비트연산은 다양한 최적화 테크닉에 있어 중요한 부분을 차지하고 있고, 의외로 많은 사람들이 헷갈려 하는 부분이당( 나 포함 )... 'IT Story > Programming Language' 카테고리의 다른 글
|
|
아주 좋은 글을 읽었다. C++ 과 C# 의 차이점 정리 이분, 개발/개발언어 관련해서 꾸준히 좋은 글들을 많이 쓰신다. 저분의 블로그는 2007 년 Tistory 100 대 블로그에 오르기도 했더군.. (부럽다 ㅋ) 마이크로소프트에서 C++ 는 Naive Language 라 하고, C# 을 Managed Language 라고 표현한 것을 읽은 적이 있다. 분명 C# 은 개발자 입장에서 더 강력한 기능을 손쉽게 제공하고, 또 버그가 날 가능성을 줄여주는 파워풀한 현대적인 개발언어임에는 분명하다. 하지만 글에서 언급한 것과 같이 컴파일 한 바이너리가 커지는 문제, C++ 에 비해 느린 실행속도 등을 고려해 볼때 임베디드 시스템 개발용 언어로는 부적합 하다고 생각된다. 아무리 임베디드 시스템의 하드웨어 성능이 점점 좋아지고 있다고 해도... 리소스를 1byte 라도 절약하고 속도를 1milli sec 라도 줄여야 하는 상황에서 소프트웨어 개발 레벨에서 부터 이런 방식으로 리소스를 갉아먹는 건 매우 좋지 않다고 본다. 내가 아는 어느 팀에서는 임베디드 시스템의 OS 로 Windows CE 를 쓰고, 개발 언어로 C# 을 쓰는데, 여러가지 고민 끝에 선택한 결과겠지만, 내가 볼때 이 결정은 개발자가 편할수는 있지만 소비자나 회사의 입장에서는 불리하다고 판단된다. Windows CE 라는 플랫폼 자체가 제품 당 OS 라이센스를 지불해야 하는 금전적 부담이 있으며, Windows CE 가 Linux 에 비해 갖는 장점은 개발자에게 친화적이고 소프트웨어 호환성이 좋다는 점 등인데, 진지하게 원가를 고려해 보고, 또 소프트웨어 변화가 적은 임베디드 환경을 생각해 본다면 좀 힘들더라도 Linux 에서 사용자가 편리한 UI 를 개발해서 서비스 하는 것이 맞다고 생각된다. 개발언어 역시 마찬가지로, C# 으로 개발을 하게 되면 개발자 입장에서 최신 언어를 사용한다는 만족감과 개발의 편리성은 증대되겠지만 제품 입장에서 보면 리소스와 퍼포먼스 측면에서 손해를 보게 된다. 추가로, 디바이스 드라이버나 연동부분의 legacy code 는 대개 C/C++ 로 되어 있어 호환성에도 문제가 생기게 된다. 결론은... 상용화 과제에 있어서는 개발자가 유리한 방향으로 개발을 해서는 안되고 사용자가 유리한 방향으로 개발이 진행되어야 하는데... 그것이 사실 쉽지 않다. 사실, 만약 Windows 개발에 익숙한 나에게 Linux 환경에서 개발해야 하는 과제가 주어지면 나 역시 불평 불만이 많을 것이다... ㅋㅋ 사실, 개발 난이도의 증가에 따른 개발비용과 리스크의 증가등도 분명 중요한 고려사항이 될 수 있다. 하지만 기술적인 어려움을 논외로 하고, 비지니스 측면을 진지하게 고려한다면 힘들더라도 항상 사용자 위주로 생각하면서 개발을 해야 할 것이다. 'IT Story > Programming Language' 카테고리의 다른 글
|
-
hyperdash
2008/07/13 21:15
댓글주소
수정/삭제
댓글쓰기
C/C++이 최고지....
VC8.0이 되면서 안전한 API들도 추가되었고
다른 언어로 대체하는 것보다는 한차원 높은 개발 방법론이 더 중요하지 않을까? -
blueecho
2008/07/15 04:28
댓글주소
수정/삭제
댓글쓰기
개인적인 생각으로도 아직은 C#은 무리인거 같아. 하지만 뭐... 혹시 알아?
시스템의 발전 속도가 너무 빨라서 말야.. -_-;;
나같은 경우도 예전에는 메모리를 줄이면서 성능을 포기하는 경우가 많았는데 요즘은 메모리 많이 쓰더라도 최대한 성능 좋은 쪽 아니면 전력소모 작은 쪽으로 가는걸로 봐서 개발에도 트랜드가 있지 않을까 싶네 그랴.. ^^;;
뭐.. 이런 문제야 실무 수행하는 자네들이 더 잘 알겠지만 말야.. 흐흐~
사용자 위주의 개발이라고 해도 개발자 입장에서는 좀 더 편하게 하고 싶은 맘도 있고 말야... 뭐, 앞으로는 다품종 소량생산이라고 하니 TTM관점에서는 개발자가 빨리 개발할 수 있는 환경도 나름대로는 의미있다고 생각되네...
하여간, 덕분에 좋은 글 읽고 간다. ^^
|
몇몇 컴파일러(VC++ 6.0, VC++ 2005 등... ) 에서 int main() 과 void main() 이 둘다 컴파일 에러없이 잘 동작하는 반면, dev C++, g++ 과 같은 컴파일러에서는 void main() 은 컴파일 에러를 낸다. 늘 습관처럼 int main() 을 쓰던터라 이번기회에 int main() 과 void main() 의 차이에 대해 자세히 찾아보았다. 우선 결론부터 말하자면 int main() 이 C/C++ 표준에 맞는 표기이며, void main() 은 잘못된 표기인 것이다. 그러므로 void main() 으로 코딩을 한다면 설사 컴파일러가 컴파일 에러를 발생시키지 않는다 할지라도 프로세스 상에서는 main() 이 올바르지 않은 종료조건을 OS 에 리턴하고 자신을 종료할 가능성이 있게 된다. 왜 그렇다면 많은 서적과 예제에서 여전히 잘못된 표기인 void main() 을 쓰고 있을까? 초창기의 C 에서는 void 타입의 함수란 존재하지 않았으며, return 이 의미가 없는 경우에는 int 형 garbage 를 리턴하는 함수를 썼다 한다. 초기의 이러한 전통때문에 리턴값이 별 의미없는 int 형 함수들이 시간이 지나면서 프로그래머들의 취향에 따라 void 형 함수들로 고쳐 사용되었는데 이 과정에서 main() 함수도 void main() 으로 많이 사용되어진 것이 아닌가 추측된다. void main() 으로 실행할 경우 default 로 정상종료를 시키는 컴파일러들이 많아짐에 따라 프로그래머들은 별 문제 없이 이러한 코딩습관을 갖게 되었고, 사용자들이 이런 습관대로 쓰는 경우가 많아지자 컴파일러 제작사들도 사용자들의 습관에 따라 void main() 을 warning 이나 컴파일 에러없이 컴파일 되도록 만들게 되었다... 이것이 유력한 주장이다. 참고 : http://www.eskimo.com/~scs/readings/voidmain.960823.html 또다른 참고 : http://libe.tistory.com/2861180 'IT Story > Programming Language' 카테고리의 다른 글
|
-
Hikikomori
2007/09/30 09:32
댓글주소
수정/삭제
댓글쓰기
히히, 전 이제 막 씨언어 접한 학생인데,
학교에선 맨날 교수님이 Visual C 로, 문자 출력시에 void main()을 쓰더라구요,
전 dev++에 익숙해서 그런지 맨날 void 쓰니까 막햇갈려서,
그래서 그냥 int main() 이것만 써요,ㅋ
왜 그럴까 궁금했는데, 이제 쫌 알겠어요, 게시글 굿또! -
hyperdash
2007/10/02 01:06
댓글주소
수정/삭제
댓글쓰기
호옷... 이거 1학년때가 생각나는군....
k = 1;
k++ + ++k = ?
++k + k++ = ?
뭐 이런거 고민하고 그랬었지... -
|
교내 프로그래밍 대회에서 내가 출제한 문제 중 하나가 바로 후위 표기법(Postfix Notation) 을 계산하는 쉬운 문제였는데, 이 문제에서 양수와 음수 모두의 사칙연산을 처리해 줘야 하는 것이 함정이라서 생각보다 정답률이 높지는 않았다. C. 후위 표기법 그런데 대회가 끝나고 나서, 이 문제에 대해 본인이 테스트 할 때는 제대로 답이 나오는데 Judge 는 오답이라고 판정하는 이유를 아무리 생각해도 모르겠다며 나를 찾아온 후배가 있었는데, 어떤 부분이 잘못 되었는지 토론하던 중에 이런 이야기가 있었다. "문제의 기술에서는 음수의 나눗셈에서 몫만 계산한다고 할때 -10 / 3 = -3 이 아닌 -4 가 될 수 있는데, 이 부분이 ambiguous 하지 않은가??" 결론적으로, 오늘 claim 을 했던 후배의 소스코드를 검토한 결과 소스코드의 오류는 divide by zero 이후의 수식을 제대로 예외처리 하지 않은 문제였다. 그런데 음수의 나눗셈에 대한 문제는 나도 좀 애매해서 인터넷을 찾아보니 다음과 같은 명쾌한 설명이 있었다. agile 이야기 :: 표면적 단순함과 심층적 단순함 : -5/4 는 얼마일까 저 글을 참고하면 결론적으로, "음수의 나눗셈은 프로그래밍 언어마다 다르게 구현된다" 가 정답이고, 실제로 수학자들 사이에서도 -10/3 = -3 인지 -10/3 = -4 인지는 논란이 되고 있으며, 일반적으로 어느 쪽으로 정의하느냐에 따라 해당 정리에 맞게 사용된다고 한다. ( C 나 C++, Java 에서는 -10 / 3 = -3 이 되고, Python, Ruby 에서는 -10 / 3 = -4 가 된다. ) 다만, 이 프로그래밍 대회에서는 "사용 언어를 C 와 C++ 로 규정지었으므로, C 와 C++ 의 나눗셈연산 결과를 사용하는 것이 맞다" 라고 결론을 지었다. 그냥 지나칠 수 있는 이런 미묘한 부분까지 덕분에 새롭게 리뷰할 수 있어서, 나 자신에게도 좋은 공부가 되는 기회가 된 것 같다. =) 사실 이 문제의 샘플 인풋에서는 의도적으로 음수의 나눗셈을 표시하지 않았는데 이것을 샘플 인풋에서 오픈했더라면 이런 오해는 없지 않았을까 하는 생각도 든다. ;) 참고 ) Wikipedia 의 Integer Division 설명 'IT Story > Programming Language' 카테고리의 다른 글
|
-
-
-
새빛 2007/11/20 12:02 댓글주소 수정/삭제 댓글쓰기
안녕하세요. floor 함수 (greatest integer less than or equal to 함수)에 대해서 검색하다 들르게 되었습니다.
이견을 말씀드리자면, 수학에서 (정수)÷(자연수) 의 몫과 나머지를 정의하는 방법은
제가 알기로는, 절대 수학자마다 다르지 않습니다.
고등학교 교과서(10-가)에서도 그렇고, 대학에서 교재로 쓰이는 정수론이나 추상대수학 책들을 보면
모두 한가지 원칙에 근거해서 정수 나눗셈을 정의하고 있습니다.
그 원칙이란 다름 아닌 "나머지의 범위가 0 이상 제수 미만"이어야 한다는 것입니다.
-1을 3으로 나누면, 몫이 0이고 나머지가 -1인게 아니라, 몫이 -1이고 나머지가 2입니다.
-2를 3으로 나누면, 몫이 0이고 나머지가 -2인게 아니라, 몫이 -1이고 나머지가 1입니다.
-3을 3으로 나누면, 몫이 -1이고 나머지가 0입니다.
-4를 3으로 나누면, 몫이 -1이고 나머지가 -1인게 아니라, 몫이 -2이고 나머지가 2입니다.
-5를 3으로 나누면, 몫이 -1이고 나머지가 -2인게 아니라, 몫이 -2이고 나머지가 1입니다.
등등등.......
이렇게 정의하는 이유는 아마도 다음과 같은 일관성을 유지하기 위한 것으로 생각됩니다.
가령 3으로 나눈다고 했을때, 가능한 나머지는 0,1,2 의 3가지입니다.
그러면 정수를 3을 가지고 3부류로 나눌 수 있는데
[3으로 나눴을때 나머지가 0인 것] = { ... , -9, -6, -3, 0, 3, 6, 9, ... } ::::: (*)
[3으로 나눴을때 나머지가 1인 것] = { ... , -8, -5, -2, 1, 4, 7, 10, ... } :::: (*) 에다 1씩 더한거입니다.
[3으로 나눴을때 나머지가 2인 것] = { ... , -7, -4, -1, 2, 5, 8, 11, ... } :::: (*) 에다 2씩 더한거입니다.
이렇게 자연수에서 가능한 것을 자연스러운 방법으로 정수로 확장할 수 있습니다.
그러나 -10 나누기 3을, 몫이 -3이고 나머지가 -1이라고, 이런 방식으로 (음수)÷(자연수)를 정의한다면
분모(피제수)가 자연수일 때 성립하는 위와 같은 성질이 그대로 성립하지 않습니다.
당장 가능한 나머지의 가짓수도 3가지에서 5가지로 늘어나죠. 복잡해집니다.
복잡해진 반면에 거기서 얻을 수 있는 것은 그다지 별로 없구요. -
새빛 2007/11/20 12:12 댓글주소 수정/삭제 댓글쓰기
정수의 나눗셈에서 논란이 되는 것은 분자(피제수)가 음수인 경우가 아니라, 분모(제수)가 음수인 경우입니다.
앞서 말한 원칙인 "0 이상 제수 미만"이라는 것 자체가 넌센스가 되어 버리니까요.
이 문제를 해결하는 방법은 두 가지가 있습니다.
첫째. 나머지의 밤위를 '0 이상 |제수| 미만' 으로 한정시키는 방법. (| | 기호는 절대값을 의미)
둘째, 나머지의 범위를 '제수 초과 0 이하'로 한정시키는 방법. (제가 기억이 확실하지 않아서... 제수 이상 0 미만일수도 있음..
-
새빛 2007/11/20 16:35 댓글주소 수정/삭제 댓글쓰기
감사합니다. 검색질을 좀 해보니, 컴퓨터 쪽에서는 몫을 0쪽으로 절삭(truncate towards zero)하는 방식으로 정수 나눗셈이 동작하는 경우가 흔하네요. 포트란 시절 부터 그래왔다고 하며, C99 표준에서도 아예 이러한 방식으로 못을 박아 버렸습니다. (C90에서는 이를 따로 정의하지 않았지만, 많은 수의 컴파일러가 이 방식으로 동작했다고 합니다.)
심지어 인텔 플랫폼의 IDIV명령(부호있는 정수 나눗셈 명령)도 이와같은 방식으로 동작하네요. 아, 그리고 C언어의 int 타입 캐스팅(혹은 int() 캐스팅 함수)도 같은 방식입니다.
이유가 뭘까 궁금해지네요. 단지 과거(이를테면 포트란, 과거에 나온 C 컴파일러)와의 호환성 때문이라고 말해버리기엔 그 이상의 뭔가 있어 보이는데... 왜냐면 포트란은 과학 기술용으로 만들어진 언어이고, 그렇기 때문에 상식적으로 생각해 봤을 때 수학에서 동작하는 방식과 같은 방식으로 정수 나눗셈이 동작하도록 했을 법한데 그렇게 하지 않았다는 것이거든요. 그렇다면 뭔가 다른 이유가 있어서 'truncate towards zero'의 방식으로 정수 나눗셈 방식을 결정한 것은 아닐까 하는 생각이 드는데, 그 이유가 뭔지는 저도 아직 잘 모르겠네요.
참고:
* [C언어의 진화] ② C99의 신기술들 (ZDnet Korea)
http://www.zdnet.co.kr/builder/dev/c/0,39030803,39131011,00.htm
* 나머지 연산자의 동작 (KLDP)
http://kldp.org/node/55978
* 나머지 연산자 (KLDP)
http://kldp.org/node/74561
* IDIV--Signed Divide (Intel)
http://www.intel.com/software/products/documentation/vlin/mergedprojects/analyzer_ec/mergedprojects/reference_olh/mergedProjects/instructions/instruct32_hh/vc135.htm
저는 수학교육을 전공하고 있는 학부생이며 프로그래밍은 취미로 이리 저리 찔러보는 정도입니다...
-
shinlucky
2008/10/15 23:08
댓글주소
수정/삭제
댓글쓰기
어셈블러에서 기계어 나머지 연산을 시스템콜을 사용하지 않고 구현하는 과제를 하던 도중인데,
덕분에 좋은 글들 읽고 갑니다.
감사합니다^^;
저도 수학전공은 아니지만 -.-;
프로그램 짜느라 짱구좀 굴려보다보면 나눗셈에서 문제가 된 것이
제수가 음수일 경우에는 문제가 되지 않지만(피제수가 양수라 가정)
정작 가장 문제가 되는 것은 피제수(나눔을 당하는수-dividend)가 음수일때 문제가 됩니다.
피제수가 음수면 제수가 양수 또는 음수일 경우
위에서 언급한 여러 프로그램들마다 다른방식으로 처리하듯이 나머지를 양수로 처리하느냐, 그 외로 처리하느냐 고민하게 됩니다.
피제수가 양수이면 제수가 양수이면 몫이 양수가 나오고, 음수이면 몫도 음수이기 때문에 나머지 연산에 지장이 없으나,
피제수가 음수일 경우 어떤 방식으로 해나갈지 큰 고민에 빠지게 됩니다.
어셈블리어의 경우 최적화된 나눗셈 알고리즘을 사용하게되는데..... 결국 C 형식으로 택하게 되었습니다.
(프로그램짜는 입장에서 그방식이 훨씬 사용성이 좋다고 생각합니다.)
아.. 잡설이었고 아무튼 좋은 링크와 글 잘보고 갑니다^^



C.pdf


