개요
오늘은 C++의 이식성에 대해 이야기하겠습니다. 일반적으로 개발에 C++를 사용하고 C++의 이식성 문제에 대해 잘 알지 못한다면 이 시리즈를 살펴보는 것이 좋습니다. 현재 크로스 플랫폼 개발이 필요하지 않더라도 이식성에 대해 아는 것은 여전히 도움이 됩니다.
C++ 이식성의 주제는 컴파일러, 운영 체제, 하드웨어 시스템 등과 같은 다양한 측면을 다루는 큰 주제입니다. 각 측면에는 많은 내용이 있습니다. 저의 능력과 에너지가 제한되어 있기 때문에 참고하실 수 있도록 각 측면에서 가장 일반적인 문제만 소개하겠습니다.
나중에 컴파일러, C++ 구문, 운영 체제, 타사 라이브러리, 보조 도구, 개발 프로세스 등의 측면에서 소개하겠습니다.
컴파일러
크로스 플랫폼 개발 과정에서 컴파일러와 관련된 문제가 많이 발생합니다. 그럼 먼저 컴파일러 관련 문제에 대해 이야기해 보겠습니다.
컴파일러 선택
우선 GCC는 거의 모든 운영 체제 플랫폼에서 사용할 수 있으므로 GCC 지원이 우선입니다. 기본적으로 범용 컴파일러가 됩니다. 여러분의 코드가 플랫폼 A에서 GCC에 의해 컴파일되고 전달될 수 있고, 그런 다음 플랫폼 B에서 유사한 버전의 GCC로 컴파일될 수 있다면 일반적으로 큰 문제가 없을 것입니다. 그러므로 GCC는 이를 지원하는 것을 반드시 고려해야 합니다.
두 번째로 로컬 컴파일러가 지원되는지 고려하세요. 소위 로컬 컴파일러는 운영 체제 제조업체에서 생산한 컴파일러입니다. 예를 들어 Windows용 기본 컴파일러는 Visual C++입니다. Solaris와 관련된 로컬 컴파일러는 SUN의 CC입니다. 성능에 더 민감하거나 일부 로컬 컴파일러의 고급 기능을 사용하려는 경우 GCC뿐만 아니라 로컬 컴파일러 지원을 고려해야 할 수도 있습니다.
컴파일 경고
컴파일러는 많은 잠재적인 문제(이식성 포함)를 감지하고 경고할 수 있습니다. 이러한 경고 메시지에 주의를 기울이면 많은 문제를 줄일 수 있습니다. 따라서 다음을 강력히 권장합니다.
1 컴파일러의 경고 수준을 높이세요.
2 컴파일러의 경고 정보를 쉽게 무시하지 마세요.
크로스 컴파일러
크로스 컴파일러의 정의는 "위키피디아"를 참조하세요. 일반인의 관점에서 이는 플랫폼 A에서 바이너리 프로그램을 컴파일하고 이를 플랫폼 B에서 실행하는 것을 의미합니다. 개발하려는 응용 프로그램이 Solaris에서 실행되지만 현재로서는 Solaris를 실행할 수 있는 SPARC 시스템이 없다고 가정합니다. 크로스 컴파일러가 유용할 수 있습니다. 일반적인 상황에서는 GCC를 사용하여 크로스 컴파일러를 만듭니다. 공간 제한으로 인해 여기에서는 이에 대해 깊이 논의하지 않습니다. 관심있는 학생들은 "여기"를 참고하세요.
예외 처리
이전 게시물 "문법"의 지면의 제약으로 인해 예외에 대해 이야기할 시간이 없었습니다. 이제 예외와 관련된 부분에 대해 별도로 이야기하겠습니다.
new가 메모리 할당에 실패할 경우 주의하세요.
초기 버전의 컴파일러에서 생성된 코드는 new가 실패할 경우 null 포인터를 반환합니다. 그 당시 제가 사용했던 볼랜드 C++ 3.1은 지금은 이런 종류의 컴파일러가 거의 없을 것 같습니다. 현재 사용하고 있는 컴파일러가 여전히 이와 같이 동작한다면 문제가 있는 것입니다. 예외 처리를 용이하게 하기 위해 bad_alloc 예외를 발생시키는 new 연산자를 오버로드하는 것을 고려할 수 있습니다.
약간 최신 컴파일러는 널 포인터만 반환하지 않습니다. new 연산자가 표준(C++03 표준 섹션 18.4.2 참조)에 따라 메모리가 긴급하다고 판단되면 new_handler 함수를 호출해야 합니다(프로토타입은 typedef void (*new_handler)();). 표준에서는 new_handler 함수가 다음 세 가지 작업을 수행하도록 권장합니다.
1. 더 많은 메모리를 확보하세요.
2. bad_alloc 예외를 발생시킵니다.
3. 프로세스를 종료합니다.
new_handler 함수는 (set_new_handler 호출을 통해) 재설정될 수 있으므로 위와 같은 동작이 나타날 수 있습니다.
요약하면 new가 메모리를 할당하는 경우 세 가지 가능성이 있습니다.
1. null 포인터를 반환합니다.
3.프로세스가 즉시 종료됩니다.
코드의 이식성을 높이려면 세 가지 상황을 모두 고려해야 합니다.
예외 사양을 주의해서 사용하세요.
제 생각에는 예외 사양이 좋지 않다고 생각하시면 "C++ 코딩 표준 - 101 규칙, 지침 및 모범 사례"의 75조를 읽어보세요. (구체적인 단점은 나중에 C++ 예외 및 오류 처리에 대한 별도의 게시물에서 논의됩니다.) 함수에 의해 발생된 예외가 포함되지 않은 경우 표준(03 표준의 18.6.2장 참조)에 따라 더 가깝습니다. 함수의 예외 사양에서 unExceptioned()를 호출해야 합니다. 그러나 모든 컴파일러 생성 코드가 표준(예: 일부 버전의 VC 컴파일러)을 준수하는 것은 아닙니다. 지원해야 하는 컴파일러가 예외 사양과 일관되지 않게 동작하는 경우 예외 사양 선언을 제거하는 것이 좋습니다.
모듈 전체에서 예외를 발생시키지 마세요.
여기서 언급된 모듈은 동적 라이브러리를 나타냅니다. 프로그램에 여러 동적 라이브러리가 포함되어 있는 경우 모듈에서 내보낸 함수 외부에서 예외를 발생시키지 마십시오. 결국 C++에 대한 ABI 표준은 아직 없으며(향후에는 없을 것으로 추정됨), 모듈 전체에 예외를 던지면 예측할 수 없는 많은 동작이 발생하게 됩니다.
구조적 예외 처리(SEH)를 사용하지 마세요
SEH에 대해 들어본 적이 없다면 제가 언급하지 않은 척하고 이 단락을 건너뛰세요. 이전에 SEH를 사용하는 데 익숙했다면 크로스 플랫폼 코드를 작성하기 전에 이 습관을 바꿔야 합니다. SEH가 포함된 코드는 Windows 플랫폼에서만 컴파일할 수 있으며 크로스 플랫폼이 아닙니다.
catch(…) 정보
논리적으로 말하면 catch(…) 문은 C++ 예외 유형만 캡처할 수 있으며 액세스 위반 및 0으로 나누기 오류와 같은 C++가 아닌 예외에 대해서는 무력합니다. 그러나 액세스 위반 및 0으로 나누기 오류와 같은 일부 경우(예: 일부 VC 컴파일러)에서는 catch(...)로 포착할 수도 있습니다. 따라서 코드를 이식 가능하게 하려면 프로그램 논리에서 위의 catch(...) 동작을 사용할 수 없습니다.
하드웨어 시스템 관련
이번에 이야기한 주제는 주로 하드웨어 시스템과 관련된 것입니다. 예를 들어, 프로그램이 다양한 유형의 CPU(x86, SPARC, PowerPC)를 지원해야 하거나 단어 길이가 다른 동일한 유형의 CPU(예: x86 및 x86-64)를 지원해야 하는 경우 하드웨어 시스템에 주의해야 합니다. .
기본 유형의 크기
C++에서 기본 유형의 크기(점유 바이트 수)는 CPU 워드 길이가 변경됨에 따라 변경됩니다. 따라서 int가 차지하는 바이트 수를 표현하려면 "4"를 직접 쓰지 말고(그런데 "4"를 직접 쓰는 것도 Magic Number의 금기에 어긋나니 자세한 내용은 여기를 참조하세요)라고 써야 합니다. " sizeof(int)"; 반대로 크기가 4바이트여야 하는 부호 있는 정수를 정의하려면 int를 직접 사용하지 말고 사전 typedef 고정 길이 유형(예: 부스트 라이브러리의 int32_t, ACE 라이브러리 ACE_INT32 등).
포인터의 크기에도 위와 같은 문제가 있다는 사실을 잊어버릴 뻔 했으니 주의하세요.
바이트 순서
"바이트 순서"를 들어본 적이 없다면 "위키피디아"를 읽어보세요. 대중적인 비유를 사용하자면, 빅엔디안 머신에 4바이트 정수 0x01020304가 있다면 네트워크나 파일을 통해 리틀엔디안 머신으로 전송될 때 0x04030201이 된다는 뜻이다. (그러나 나는 그것을 접해본 적이 없다.) 위의 정수는 0x02010403이 될 것이다.
만약 당신이 작성한 애플리케이션이 네트워크 통신을 포함한다면, 호스트 코드와 네트워크 코드 사이에서 이를 변환해야 한다는 것을 기억해야 합니다. 기계 간에 바이너리 파일을 전송하는 것과 관련된다면, 유사한 변환도 수행해야 한다는 것을 기억해야 합니다.
메모리 정렬
"메모리 정렬"이 무엇인지 모르신다면 "위키피디아"를 읽어보세요. 간단히 말하면, CPU 처리 성능을 위해 구조 내 데이터는 서로 가깝지 않고 어느 정도 공간을 두고 분리되어야 한다. 이 경우 구조의 각 데이터 주소는 정확히 특정 단어 길이의 정수배입니다.
메모리 정렬의 세부 사항은 C++ 표준에 정의되어 있지 않으므로 코드는 정렬 세부 사항에 의존할 수 없습니다. 구조체의 크기를 계산할 때마다 sizeof()는 정직하게 작성됩니다.
일부 컴파일러는 #pragma pack 전처리 문(정렬 단어 길이를 수정하는 데 사용할 수 있음)을 지원하지만 이 구문은 모든 컴파일러에서 지원되지 않으므로 주의해서 사용하세요.
시프트 연산
부호 있는 정수의 오른쪽 시프트 연산의 경우 일부 시스템에서는 기본적으로 산술 오른쪽 시프트를 사용하고(가장 높은 부호 비트는 변경되지 않음) 일부 시스템에서는 기본적으로 논리적 오른쪽 시프트를 사용합니다(가장 높은 부호 비트는 0으로 채워짐). 따라서 부호 있는 정수에서는 오른쪽 시프트를 수행하지 마세요. 그런데 이식성 문제가 없더라도 코드에서 시프트 연산자를 가능한 한 적게 사용하십시오. 성능을 향상시키기 위해 교대 연산을 사용하려는 사람들은 가독성이 좋지 않을 뿐만 아니라 감사할 일도 아닙니다. 컴파일러가 정신적으로 너무 지체되지 않는 한, 프로그래머가 걱정할 필요 없이 자동으로 이 최적화를 처리합니다.
운영체제
이전 게시물에서는 "하드웨어 시스템"과 관련된 주제를 언급했습니다. 오늘은 운영체제와 관련된 주제에 대해 이야기해 보겠습니다. C++ 크로스플랫폼 개발에는 OS에 관련된 자잘한 사항이 많아서 오늘은 장황하게 설명드리니 양해 부탁드립니다 :-)
이후에는 혼동을 피하기 위해 리눅스와 각종 유닉스를 통칭하여 지칭합니다. Posix 시스템으로.
파일 시스템(이하 FS)
이제 막 크로스 플랫폼 개발을 시작한 대부분의 초보자는 FS와 관련된 문제에 직면하게 됩니다. 그럼 먼저 FS에 대해 이야기해 보겠습니다. 요약하면, 개발 중에 쉽게 접할 수 있는 FS 차이점은 주로 다음과 같습니다: 디렉터리 구분 기호의 차이, 대소문자 구분의 차이, 경로에서 금지된 문자의 차이.
위의 차이점에 대처하려면 다음 사항에 주의해야 합니다.
1. 파일 및 디렉터리 이름은 표준화되어야 합니다.
파일 및 디렉터리 이름을 지정할 때는 문자와 숫자만 사용하도록 하세요. 이름이 비슷한 두 파일(이름의 유일한 차이점은 foo.cpp와 Foo.cpp 등)을 같은 디렉터리에 두지 마십시오. 일부 OS 예약어(예: aux, con, nul, prn)를 파일 이름이나 디렉터리 이름으로 사용하지 마십시오.
추가하자면 방금 언급한 이름에는 소스 코드 파일, 바이너리 파일 및 런타임 중에 생성된 기타 파일이 포함됩니다.
2. #include 문은 표준화되어야 합니다.
#include 문을 작성할 때 백슬래시 ""(Windows에서만 사용 가능) 대신 슬래시 "/"(더 일반적임)를 사용하도록 주의하세요. #include 문의 파일 및 디렉터리 이름은 실제 이름과 정확히 동일한 대소문자를 사용해야 합니다.
3. 코드에 FS 작업이 포함된 경우 기성 라이브러리를 사용해 보세요
FS를 위한 성숙한 타사 라이브러리(예: Boost::filesystem)가 이미 많이 있습니다. 코드에 FS 작업(예: 디렉터리 탐색)이 포함된 경우 이러한 타사 라이브러리를 사용해 보십시오. 이렇게 하면 많은 작업을 줄일 수 있습니다.
★텍스트 파일의 캐리지 리턴 CR/라인 피드 LF
몇몇 잘 알려진 운영 체제가 캐리지 리턴/라인 피드를 일관되지 않게 처리하기 때문에 이러한 귀찮은 문제가 발생합니다. 현재 상황은 다음과 같습니다. Windows는 CR과 LF를 모두 사용하고 대부분의 Unix는 LF를 사용합니다.
소스 코드 관리의 경우 다행스럽게도 많은 버전 관리 소프트웨어(예: CVS, SVN)가 이 문제를 지능적으로 처리하므로 코드 베이스에서 로컬 소스 코드를 검색하여 로컬 형식에 맞출 수 있습니다.
프로그램이 런타임에 텍스트 파일을 처리해야 하는 경우 텍스트 모드로 여는 것과 바이너리 모드로 여는 것의 차이점에 주의하세요. 또한 다른 시스템 간에 텍스트 파일을 전송하는 경우 적절한 처리를 고려하십시오.
★파일 검색 경로(실행 파일 및 동적 라이브러리 검색 포함)
Windows에서는 파일을 실행하거나 동적 라이브러리를 로드하려는 경우 일반적으로 현재 디렉터리를 검색하지는 않습니다. Posix 시스템의 경우입니다. 따라서 애플리케이션에 프로세스 시작이나 동적 라이브러리 로드가 포함된 경우 이 차이점에 주의하세요.
★환경 변수
위에서 언급한 검색 경로 문제와 관련하여 일부 학생들은 PATH 및 LD_LIBRARY_PATH를 수정하여 현재 경로를 도입하고 싶어합니다. 이 방법을 사용하는 경우에는 프로세스 수준의 환경 변수만 수정하고 시스템 수준의 환경 변수는 수정하지 않는 것이 좋습니다(시스템 수준의 수정은 동일한 시스템의 다른 소프트웨어에 영향을 주어 부작용을 일으킬 수 있음).
★동적 라이브러리
애플리케이션이 동적 라이브러리를 사용하는 경우 동적 라이브러리에서 표준 C 스타일 함수를 내보내는 것이 좋습니다(클래스를 내보내지 마십시오). Posix 시스템에 동적 라이브러리를 로드하는 경우 RTLD_GLOBAL 플래그를 주의해서 사용해야 합니다. 이 플래그는 전역 기호 테이블을 활성화하여 여러 동적 라이브러리 간에 기호 이름 충돌을 일으킬 수 있습니다(이런 일이 발생하면 엄청난 런타임 오류가 발생하여 디버그하기가 매우 어렵습니다).
★서비스/가디언 프로세스
서비스 및 가드 프로세스의 개념이 명확하지 않은 경우 Wikipedia(여기 및 여기)를 읽어보세요. 이하에서는 설명의 편의를 위해 서비스를 통칭합니다.
C++로 개발된 모듈은 대부분 백그라운드 모듈이기 때문에 서비스 문제가 자주 발생합니다. 서비스를 작성하려면 여러 시스템 관련 API를 호출해야 하므로 운영 체제와 긴밀하게 결합되어 하나의 코드 세트를 사용하기가 어렵습니다. 따라서 더 나은 방법은 일반 서비스 셸을 추상화한 다음 그 아래에 비즈니스 논리 코드를 동적 라이브러리로 탑재하는 것입니다. 이 경우 최소한 한 세트의 비즈니스 논리 코드가 보장됩니다. 두 세트의 서비스 셸 코드(Windows용 하나, Posix용 하나)가 필요하지만 비즈니스 독립적이며 쉽게 재사용할 수 있습니다.
★기본 스택 크기
운영 체제에 따라 기본 스택 크기는 수십 KB(Symbian은 12K만 지원한다고 하는데 정말 인색함)부터 수 MB까지 크게 다릅니다. 따라서 대상 시스템의 기본 스택 크기에 대해 미리 문의해야 합니다. Symbian과 같이 인색한 시스템을 만나면 컴파일러 옵션을 사용하여 이를 늘리는 것을 고려할 수 있습니다. 물론 "스택에 큰 배열/큰 개체를 정의하지 않는" 좋은 습관을 기르는 것도 중요합니다. 그렇지 않으면 스택이 아무리 크더라도 버스트가 발생합니다.
multithreading
지난 한 달간 작성한 게시물이 상대적으로 혼합되어 있어 오랫동안 이 시리즈가 업데이트되지 않았습니다. 그러다 보니 또 다른 네티즌이 댓글로 저를 독려해 조금 당황스럽기도 했습니다. 오늘 멀티스레딩 장을 서둘러 작성하세요. 지난번 운영체제 이야기를 했을 때 운영체제와 관련된 주제는 상대적으로 사소했기 때문에 잡다한 주제를 많이 이야기했습니다. 당시 글이 좀 긴 것을 보고 멀티 프로세스와 멀티 스레딩 부분은 나중에 남겨 두었습니다.
★Compiler
◇C 런타임 라이브러리 옵션 정보
먼저 매우 기본적인 질문인 C 런타임 라이브러리(이하 CRT:C Run-Time)의 설정에 대해 이야기해 보겠습니다. 이런 낮은 수준의 문제를 이야기하고 싶지는 않았지만 제 주변에도 이곳에서 피해를 입은 분들이 여럿 있으니 얘기해 보는 게 좋을 것 같습니다.
대부분의 C++ 컴파일러에는 CRT(아마도 두 개 이상)가 함께 제공됩니다. 일부 컴파일러와 함께 제공되는 CRT는 스레드 지원에 따라 단일 스레드 CRT와 다중 스레드 CRT로 구분될 수 있습니다. 멀티스레드 개발을 수행하려면 관련 C++ 프로젝트가 멀티스레드 CRT를 사용하는지 확인하는 것을 잊지 마세요. 그렇지 않으면 추악한 죽음이 될 것입니다.
특히 Visual C++를 사용하여 엔지니어링 프로젝트를 만들 때는 더욱 주의해야 합니다. 새로 생성된 프로젝트에 MFC(콘솔 프로젝트 및 Win32 프로젝트 포함)가 포함되어 있지 않은 경우 프로젝트의 기본 설정은 아래 그림과 같이 "단일 스레드 CRT"를 사용하는 것입니다.
◇최적화 옵션 정보
"최적화 옵션"은 또 다른 중요한 컴파일러 관련 주제입니다. 일부 컴파일러는 훌륭하다고 알려진 최적화 옵션을 제공하지만 일부 최적화 옵션에는 잠재적인 위험이 있을 수 있습니다. 컴파일러는 자체적으로 명령 실행 순서를 방해하여 예상치 못한 스레드 경합 조건을 초래할 수 있습니다(경합 조건, 자세한 설명은 "여기" 참조). Liu Weipeng은 "C++ 다중 스레드 메모리 모델"에서 몇 가지 일반적인 예를 살펴보겠습니다.
컴파일러의 일반 속도 최적화 옵션만 사용하는 것이 좋습니다. 다른 고급 최적화 옵션의 추가 효과는 명확하지 않을 수 있지만 잠재적인 위험은 적지 않습니다. 실제로 위험을 감수할 가치가 없습니다.
GCC를 예로 들면: -O2 옵션을 사용하는 것이 좋습니다(사실 -O2는 여러 옵션의 모음입니다). -O3을 사용할 위험은 없습니다(아주 타당한 이유가 없는 한). . -O2 및 -O3 외에도 GCC에는 수많은(수백 개로 추정) 다른 최적화 옵션이 있습니다. 옵션 중 하나를 사용하려는 경우 먼저 해당 옵션의 특성과 가능한 부작용을 이해해야 합니다. 그렇지 않으면 앞으로 어떻게 죽을지 알 수 없습니다.
★스레드 라이브러리 선택
현재 C++ 03 표준에는 스레드 관련 내용이 거의 포함되어 있지 않기 때문에 (향후 C++ 0x에 스레드에 대한 표준 라이브러리가 포함되더라도 컴파일러 제조업체의 지원이 단기적으로 포괄적이지 않을 수 있음) , 따라서 앞으로도 오랜 시간이 걸릴 것입니다. 크로스 플랫폼 멀티스레딩 지원은 여전히 타사 라이브러리에 달려 있습니다. 그래서 스레드 라이브러리의 선택이 매우 중요합니다. 다음은 잘 알려진 여러 크로스 플랫폼 스레드 라이브러리에 대한 간략한 소개입니다.
◇ACE
먼저 오랜 역사를 지닌 도서관인 에이스(ACE)에 대해 이야기해보자. 이전에 접해본 적이 없다면 Literacy "여기"를 살펴보는 것부터 시작하세요. ACE(Adaptive Communication Environment)의 풀네임으로 보아 '통신'을 기반으로 해야 한다. 그러나 "멀티스레딩"이라는 부업에 대한 ACE의 지원은 뮤텍스 잠금(ACE_Mutex), 조건 변수(ACE_Condition), 세마포어(ACE_Semaphore), 장벽(ACE_Barrier), 원자적 연산(ACE_Atomic_Op) 등과 같이 여전히 매우 포괄적입니다. . ACE_Mutex와 같은 특정 유형은 스레드 읽기-쓰기 잠금(ACE_RW_Thread_Mutex), 스레드 재귀 잠금(ACE_Recursive_Thread_Mutex) 등으로 세분화됩니다.
포괄적인 지원 외에도 ACE에는 또 다른 분명한 장점이 있습니다. 즉, 다양한 운영 체제 플랫폼과 자체 컴파일러를 훌륭하게 지원한다는 것입니다. 일부 구식 컴파일러(예: VC6)를 포함하여 이를 지원할 수도 있습니다(여기에 언급된 지원은 컴파일할 수 있을 뿐만 아니라 안정적으로 실행할 수 있음을 의미합니다). 이러한 이점은 크로스 플랫폼 개발에서 매우 분명합니다.
단점은 무엇인가요? ACE가 아주 일찍(아마 1990년대 중반에) 시작된 이래로 C++의 오래된 기능 중 상당수는 아직 출시되지 않았습니다(새로운 기능은 말할 것도 없고). 따라서 ACE의 전반적인 스타일은 구식이고 부스트하기에는 훨씬 열등한 느낌을 받았습니다. 매우 패셔너블하고 아방가르드합니다.
◇boost::thread
boost::thread는 ACE와 뚜렷한 대조를 이룹니다. 이 기능은 부스트 버전 1.32부터 도입된 것으로 보이며 ACE보다 젊습니다. 하지만 Boost 전문가 그룹의 지원 덕분에 개발 속도가 상당히 빠릅니다. 현재 Boost 버전 1.38부터는 많은 기능도 지원할 수 있습니다(그러나 ACE만큼 많지는 않은 것 같습니다). C++ 표준 위원회의 많은 구성원이 부스트 커뮤니티에 모인다는 사실을 고려하면, 시간이 지남에 따라 Boost::thread는 결국 C++ 스레드의 떠오르는 스타가 될 것이며 밝은 미래가 있을 것입니다!
boost::thread의 단점은 충분한 컴파일러, 특히 일부 구식 컴파일러를 지원하지 않는다는 것입니다(대부분 고급 템플릿 구문을 사용하기 때문에 많은 Boost 하위 라이브러리에서 이 문제가 발생합니다). 이는 크로스 플랫폼의 명백한 문제입니다.
◇wxWidgets 및 QT
wxWidgets 및 QT는 모두 GUI 인터페이스 라이브러리이지만 스레드에 대한 지원도 내장되어 있습니다. wxWidgets 스레드에 대한 소개는 "여기"를 참조하고, QT 스레드에 대한 소개는 "여기"를 참조하세요. 이 두 라이브러리는 스레드에 대해 유사한 지원을 제공하며 둘 다 뮤텍스, 조건 및 세마포어와 같이 일반적으로 사용되는 메커니즘을 제공합니다. 하지만 ACE만큼 기능이 풍부하지는 않습니다.
◇무게 측정 방법
GUI 소프트웨어를 개발하고 이미 wxWidgets 또는 QT를 사용하고 있는 경우 내장 스레드 라이브러리를 직접 사용할 수 있습니다(기본 스레드 기능만 사용하는 경우). 내장된 스레드 라이브러리로 인해 기능이 약간 얇습니다. 고급 스레딩 기능이 필요한 경우 이를 Boost::thread 또는 ACE로 교체하는 것을 고려해보세요.
Boost::thread와 ACE 사이의 선택은 주로 소프트웨어의 요구 사항에 따라 다릅니다. 많고 복잡한 플랫폼을 지원하려면 컴파일러에서 지원하지 않는 문제를 피하기 위해 ACE를 사용하는 것이 좋습니다. 몇 가지 주류 플랫폼(예: Windows, Linux, Mac)만 지원해야 하는 경우 Boost::thread를 사용하는 것이 좋습니다. 결국 주류 운영 체제의 컴파일러는 여전히 부스트를 잘 지원합니다.
★프로그래밍 주의 사항
사실 멀티스레드 개발에서는 주의해야 할 점이 많습니다. 더 인상적인 점 몇 가지만 간단히 나열하겠습니다.
◇휘발성에 대하여
멀티스레드 프로그래밍에서 만날 수 있는 함정에 관해 이야기하자면, 휘발성 키워드를 언급해야 합니다. 그것에 대해 많이 알지 못한다면 먼저 "여기"를 읽고 그것에 대해 알아보십시오. C++ 98 및 C++ 03 표준은 모두 다중 스레드 메모리 모델을 정의하지 않으므로 표준에는 약간의 휘발성 및 스레딩만 있습니다. 결과적으로 C++ 커뮤니티의 많은 타액은 (많은 C++ 전문가의 타액을 포함하여) 휘발성에 초점을 맞추고 있습니다. 이러한 점을 고려하여 여기서는 자세한 내용을 다루지 않겠습니다. 몇 가지 훌륭한 기사를 추천합니다. Andrei Alexandrescu의 기사 "Here"와 Hans Boehm의 기사 "Here" 및 "Here"입니다. 여러분, 직접 가서 읽어보세요.
◇원자적 연산에 대하여
일부 학생들은 여러 스레드에 의한 쓰기 경쟁에 잠금이 필요하다는 것만 알고 있을 뿐, 여러 읽기와 단일 쓰기도 보호해야 한다는 사실은 모릅니다. 예를 들어, 동시 상태에서 정수 int nCount = 0x01020304가 있고 쓰기 스레드는 해당 값 nCount = 0x05060708을 수정합니다. 그렇다면 읽기 스레드가 "잘못된"(예: 0x05060304) 데이터를 읽는 것이 가능합니까?
데이터의 손상 여부는 nCount의 읽기 및 쓰기가 원자적 작업인지 여부에 따라 다릅니다. 이는 많은 하드웨어 관련 요소(CPU 유형, CPU 워드 길이, 메모리 정렬 바이트 수 등)에 따라 달라집니다. 어떤 경우에는 데이터 손상이 실제로 발생할 수도 있습니다.
크로스 플랫폼 개발에 대해 이야기하고 있으므로 앞으로 코드가 어떤 하드웨어 환경에서 실행될지는 신이 아실 것입니다. 따라서 유사한 문제를 처리할 때 안전을 보장하기 위해 타사 라이브러리(예: ACE의 Atomic_Op)에서 제공하는 원자 연산 클래스/함수를 사용해야 합니다.
◇객체 소멸에 대하여
이전 시리즈 "C++ 객체는 어떻게 죽는가?"에서는 Win32 플랫폼과 Posix 플랫폼에서 스레드가 부자연스럽게 소멸되는 문제가 각각 소개되었습니다.
위에서 언급한 크로스 플랫폼 스레드 라이브러리의 하위 계층은 여전히 운영 체제와 함께 제공되는 스레드 API를 호출해야 하기 때문에 모든 스레드가 자연스럽게 죽을 수 있도록 모든 사람이 최선을 다해야 합니다.
관련 추천:
위 내용은 C++의 이식성 및 크로스 플랫폼 개발(긴 기사)의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!