C 및 C++에서 유니언의 목적
나는 일찍이 편안하게 노동조합을 사용해왔다. 오늘 내가 이 글을 읽고 이 코드를 알게 되었을 때 나는 깜짝 놀랐다.
union ARGB
{
uint32_t colour;
struct componentsTag
{
uint8_t b;
uint8_t g;
uint8_t r;
uint8_t a;
} components;
} pixel;
pixel.colour = 0xff040201; // ARGB::colour is the active member from now on
// somewhere down the line, without any edit to pixel
if(pixel.components.a) // accessing the non-active member ARGB::components
실제로 정의되지 않은 행동 즉, 최근에 정의되지 않은 행동을 유도하기 위해 작성된 것 이외의 조합원으로부터 읽는 것이다.이것이 노동조합의 의도된 용도가 아니라면, 무엇인가?좀 더 자세히 설명해 주시겠습니까?
업데이트:
나는 나중에 몇 가지를 분명히 하고 싶었다.
- 그 질문에 대한 답은 C와 C++가 같지 않다; 나의 무지한 젊은이는 C와 C++ 둘 다라고 꼬리표를 붙였다.
- C++11의 표준을 훑어본 후 나는 비활동적인 조합원이 정의되지 않은/지정되지 않은/이행 정의되어 있다고 단정적으로 말할 수 없었다.내가 찾을 수 있는 건 제9.5/1조뿐이었어.
표준-레이아웃 조합이 공통의 초기 순서를 공유하는 여러 표준 레이아웃 구조체를 포함하고, 이 표준 레이아웃 조합 유형의 물체가 표준 레이아웃 구조체 중 하나를 포함하는 경우, 표준 레이아웃 구조 구성원의 공통 초기 시퀀스를 검사할 수 있다.§9.2/19: 두 개의 표준 레이아웃 구조물은 해당 구성원이 배치 호환 유형을 가지고 있고 어느 구성원도 비트 필드가 아니거나 둘 다 하나 이상의 초기 구성원의 시퀀스에 대해 같은 폭을 가진 비트 필드인 경우 공통의 초기 시퀀스를 공유한다.
- C에 있는 동안(C99 TC3 - DR 283 이후) 그렇게 하는 것이 합법적이다(이 문제를 제기하는 Pascal Cuoq 덕분에).그러나 이 작업을 시도하면 값이 읽혀지는 유형에 대해 유효하지 않은 값(일명 "트랩 표현")이 발생하는 경우에도 정의되지 않은 동작으로 이어질 수 있다.그렇지 않으면, 가치판독은 구현이 정의된다.
C89/90은 불특정 행위(Annex J)로 이것을 호명했고 K&R의 책에는 구현이 정의되어 있다고 되어 있다.K&R의 인용:
이것이 조합의 목적이다. - 여러 유형 중 하나를 합법적으로 수용할 수 있는 단일 변수. [...] 용도가 일관적인 한, 검색된 유형은 가장 최근에 저장한 유형이어야 한다.현재 조합에 저장되어 있는 유형을 추적하는 것은 프로그래머의 책임이다; 어떤 것을 하나의 유형으로 저장하고 다른 유형으로 추출하면 그 결과는 구현에 의존한다.
스트루스트럽의 TC++PL에서 추출(내 것 강조)
유니언의 사용은 데이터 호환성에 필수적일 수 있다[...] 때로는 "유형 변환"에 악용되기도 한다.
무엇보다도 이 질문(내 질문 이후 제목이 변하지 않는 질문)은 노동조합의 목적을 이해하려는 의도로 제기되었으며, 예를 들어 표준이 허용하는 것이 아니다.상속을 코드 재사용을 위해 사용하는 것은 물론 C++ 표준에서 허용되지만, 상속을 C+++ 언어 기능으로 도입하려는 목적이나 본래의 의도는 아니었다.이것이 안드레이의 대답이 계속 받아들여지는 이유다.
노동조합의 목적은 다소 분명하지만, 어떤 이유에서인지 사람들은 그것을 꽤 자주 놓친다.
유니온의 목적은 동일한 메모리 영역을 사용하여 서로 다른 시간에 다른 물체를 저장함으로써 메모리를 절약하는 것이다.바로 그겁니다.
마치 호텔의 방 같다.다른 사람들이 과대평가되지 않는 기간 동안 그 안에서 산다.이 사람들은 결코 만나지 않고, 대체로 서로에 대해 아무것도 모른다.객실의 시간 공유를 적절히 관리함으로써(즉, 서로 다른 사람들이 동시에 한 객실에 배정되지 않도록 함으로써), 비교적 작은 호텔이 비교적 많은 사람에게 숙소를 제공할 수 있는데, 이것이 바로 호텔의 목적이다.
그게 바로 조합이 하는 일이다.프로그램의 여러 개 객체가 오버랩되지 않는 값-수명을 가진 값을 가지고 있다는 것을 알고 있다면, 이러한 객체를 조합으로 "merge"하여 메모리를 절약할 수 있다.호텔 객실이 매 순간 "활성" 임차인을 1명 이상 보유하는 것처럼, 조합은 프로그램 시간마다 매 순간 "활성" 구성원을 1명 이상 보유한다."활성" 멤버만 읽을 수 있다.다른 멤버에 기록하여 "활성" 상태를 다른 멤버로 전환하십시오.
웬일인지 노조원 한 명을 작성해 다른 조합원을 통해 점검하는 이런 본래 취지는 전혀 다른 것으로 '오버라이드'됐다.이런 종류의 기억 재해석(일명 '타입 펀칭'이라 한다.
노조의 유효한 사용이 아니다. 그것은 일반적으로 정의되지 않은 행동으로 이어진다. C89/90에서 구현 정의 동작을 생성하는 것으로 설명된다.
편집: 유형 펀닝을 목적으로 유니언을 사용하는 것(즉, 한 멤버를 작성한 다음 다른 멤버를 읽는 것)은 C99 표준에 대한 기술 코리젠다 중 하나에 더 자세한 정의를 내렸다(DR#257 및 DR#283 참조).단, 이것은 공식적으로 함정의 표현을 읽으려고 시도함으로써 정의되지 않은 행동에 부딪히는 것을 보호하지 않는다는 것을 명심하라.
의 가장 일반적인 용법union
나는 정기적으로 앨리어싱이라는 것을 알게 된다.
다음을 고려하십시오.
union Vector3f
{
struct{ float x,y,z ; } ;
float elts[3];
}
이게 무슨 소용이야?의 깨끗하고 깔끔한 접근을 가능하게 한다.Vector3f vec;
의 구성원 이름:
vec.x=vec.y=vec.z=1.f ;
또는 배열로 정수 액세스
for( int i = 0 ; i < 3 ; i++ )
vec.elts[i]=1.f;
어떤 경우에는 이름으로 접속하는 것이 가장 확실한 일이다.다른 경우, 특히 축을 프로그래밍 방식으로 선택할 때, 더 쉽게 할 수 있는 것은 숫자 지수로 축에 접근하는 것이다 - x의 경우 0, y의 경우 1, z의 경우.
노동조합을 사용하여 다음과 같은 구조체를 만들 수 있는데, 여기에는 실제로 어떤 조합의 구성요소가 사용되는지 알려주는 필드가 포함되어 있다.
struct VAROBJECT
{
enum o_t { Int, Double, String } objectType;
union
{
int intValue;
double dblValue;
char *strValue;
} value;
} object;
다른 사람들이 언급했듯이, 조합은 열거형과 결합되어 구조체로 포장되어 태그가 붙은 조합을 구현하는 데 사용될 수 있다.한 가지 실용적인 용도는 러스트의 제품을 구현하는 것이다.Result<T, E>
, 이것은 원래 순수함을 사용하여 구현된다.enum
(러스트에는 열거형 변형의 추가 데이터가 포함될 수 있음)다음은 C++ 예:
template <typename T, typename E> struct Result {
public:
enum class Success : uint8_t { Ok, Err };
Result(T val) {
m_success = Success::Ok;
m_value.ok = val;
}
Result(E val) {
m_success = Success::Err;
m_value.err = val;
}
inline bool operator==(const Result& other) {
return other.m_success == this->m_success;
}
inline bool operator!=(const Result& other) {
return other.m_success != this->m_success;
}
inline T expect(const char* errorMsg) {
if (m_success == Success::Err) throw errorMsg;
else return m_value.ok;
}
inline bool is_ok() {
return m_success == Success::Ok;
}
inline bool is_err() {
return m_success == Success::Err;
}
inline const T* ok() {
if (is_ok()) return m_value.ok;
else return nullptr;
}
inline const T* err() {
if (is_err()) return m_value.err;
else return nullptr;
}
// Other methods from https://doc.rust-lang.org/std/result/enum.Result.html
private:
Success m_success;
union _val_t { T ok; E err; } m_value;
}
당신이 말하는 것처럼, 이것은 많은 플랫폼에서 "작동"하겠지만, 엄밀히 정의되지 않은 행동이다.노조를 이용하는 진짜 이유는 변종 레코드를 만들기 위해서다.
union A {
int i;
double d;
};
A a[10]; // records in "a" can be either ints or doubles
a[0].i = 42;
a[1].d = 1.23;
물론 이 변종이 실제로 무엇을 포함하고 있는지 말해줄 어떤 종류의 판별기도 필요하다.그리고 C++ 조합에서는 POD 유형, 즉 시공자와 파괴자가 없는 유형만 포함할 수 있기 때문에 그다지 유용하지 않다는 점에 유의하십시오.
그 행동은 정의되지 않을 수도 있지만, 그것은 단지 "표준"이 없다는 것을 의미한다.모든 괜찮은 컴파일러들은 패킹과 정렬을 조절하기 위해 #프라그마스를 제공하지만 디폴트(채무불이행)가 다를 수 있다.기본값은 또한 사용되는 최적화 설정에 따라 변경된다.
또한, 조합은 단지 공간을 절약하기 위한 것만은 아니다.그들은 타이프 펀칭으로 현대 컴파일러들을 도울 수 있다.네가 만약reinterpret_cast<>
컴파일러가 당신이 하는 일에 대해 추측할 수 없는 모든 것.그것은 당신의 타입에 대해 알고 있는 것을 버리고 다시 시작해야 할지도 모른다. (메모리에 쓰기를 강요하는 것은 오늘날 CPU 클럭 속도에 비해 매우 비효율적이다.)
C에서 그것은 변종과 같은 것을 구현하는 좋은 방법이었다.
enum possibleTypes{
eInt,
eDouble,
eChar
}
struct Value{
union Value {
int iVal_;
double dval;
char cVal;
} value_;
possibleTypes discriminator_;
}
switch(val.discriminator_)
{
case eInt: val.value_.iVal_; break;
리틀 메모리 시대에 이 구조는 모든 구성원을 가진 구조체보다 적은 메모리를 사용하고 있다.
C가 제공하는 방법
typedef struct {
unsigned int mantissa_low:32; //mantissa
unsigned int mantissa_high:20;
unsigned int exponent:11; //exponent
unsigned int sign:1;
} realVal;
비트 값에 액세스하려면 다음을 수행하십시오.
1974년 문서화된 C 언어에서, 모든 구조 구성원은 공통 네임스페이스를 공유했으며, "ptr->member"의 의미는 구성원의 변위를 "ptr"에 추가하고 구성원의 유형을 이용하여 결과 주소에 접속하는 것으로 정의되었다.이 설계는 동일한 ptr을 다른 구조 정의에서 가져온 멤버 이름과 동일한 오프셋으로 사용할 수 있게 해주었고, 프로그래머들은 그 기능을 다양한 용도로 사용했다.
구조물 구성원이 각자의 네임스페이스를 배정받았을 때, 같은 변위를 가진 구조물 구성원 2명을 신고하는 것은 불가능해졌다.언어에 결합을 추가함으로써 이전 버전의 언어에서 사용할 수 있었던 동일한 의미론을 달성할 수 있게 되었다(동일한 문맥으로 이름을 내보낼 수 없는 경우에도 foo-> 멤버를 foo->type1.member로 대체하기 위한 찾기/교체가 여전히 필요했을 수 있다).중요한 것은 노동조합을 추가한 사람들이 특정한 목표 용도를 염두에 두고 있는 것이 아니라, 초기 의미론에 의존했던 프로그래머들이 어떤 목적을 가지고든, 그것을 하기 위해 다른 구문을 사용해야 하더라도 여전히 같은 의미론을 달성할 수 있는 수단을 제공하는 것이었다.
비록 이것이 엄격히 정의되지 않은 행동이지만, 실제로는 거의 모든 컴파일러와 함께 작동할 것이다.그것은 너무나 널리 사용되는 패러다임이기 때문에 자존심을 존중하는 컴파일러는 이와 같은 경우에 "올바른 일"을 할 필요가 있을 것이다.그것은 확실히 어떤 컴파일러들과 함께 고장난 코드를 만들어낼 수 있는 타입-퍼닝보다 선호된다.
다른 이들은 구조 차이점을 언급했다.
나는 변수에 대한 기억이 공유되기 때문에, 그 다음 한 변수에 쓰임으로써 다른 변수들이 변화하고, 그 종류에 따라 그 값이 무의미해질 수 있다는 문제를 읽었다.
예:{ float f; int i; } x;
x.i에 쓰는 것은 만약 당신이 x.f에서 읽는다면 의미가 없을 것이다 - 그것이 당신이 플로트의 부호, 지수 또는 맨티사 구성요소를 보기 위해서 의도한 것이 아니라면.
나는 또한 정렬의 문제가 있다고 생각한다: 만약 어떤 변수들이 단어 정렬을 해야 한다면 당신은 예상된 결과를 얻지 못할 수도 있다.
예: { char c[4]; int i; } x;
가정적으로 어떤 기계에서 문자가 정렬되어야 한다면 c[0]와 c[1]는 스토리지를 i와 공유하지만 c[2]와 c[3]는 공유하지 않는다.
그 행동은 언어의 관점에서 정의되지 않았다.플랫폼마다 메모리 정렬과 엔디안성에 서로 다른 제약이 있을 수 있다는 점을 고려하십시오.빅 엔디안 대 리틀 엔디안 머신의 코드는 구조물의 값을 다르게 갱신할 것이다.언어로 동작을 수정하려면 모든 구현이 동일한 Endianness(및 메모리 정렬 제약 조건...) 제한 사용을 사용해야 한다.
C++(두 개의 태그를 사용하고 있음)를 사용하면서 휴대성에 정말 신경을 쓴다면, 구조체를 사용하면서 세터를 제공하기만 하면 된다.uint32_t
비트마스크 연산을 통해 필드를 적절하게 설정하십시오.기능만 있으면 C에서도 같은 일을 할 수 있다.
편집: 나는 AProgrammer가 투표에 대한 답을 쓰고 이것을 끝낼 것을 기대했다.일부 지적했듯이, 각 구현이 무엇을 해야 할지를 결정하도록 하여 표준의 다른 부분에서 엔디안성을 다루며, 정렬과 패딩도 다르게 처리할 수 있다.이제, AProgrammer가 암묵적으로 언급하는 엄격한 앨리어싱 규칙이 여기서 중요한 포인트다.컴파일러는 변수의 수정(또는 수정의 결여)에 대해 가정을 할 수 있다.조합의 경우, 컴파일러는 지시사항을 재정렬하고 쓰기 위에 있는 각 색상 구성요소의 판독치를 색상 변수에 이동시킬 수 있다.
C++에서는 부스트 변종이 정의되지 않은 행동을 최대한 방지하기 위해 설계된 안전한 버전의 유니언을 구현한다.
의 연주는 하다.enum + union
등, (stack all too so all) 의 template 를 사용한다.enum
:)
조합은 다음과 같은 두 가지 주요 이유로 사용할 수 있다.
- 예시와 같이 동일한 데이터에 다양한 방식으로 액세스하기 위한 편리한 방법
- 하나의 데이터 구성원만 '활성화'할 수 있는 서로 다른 데이터 구성원이 있을 때 공간을 절약하는 방법
1 대상 시스템의 메모리 아키텍처가 어떻게 작동하는지 알고 있다는 근거로 쓰기 코드를 짧게 자르는 C 스타일의 해킹에 가깝다.이미 말했듯이, 실제로 많은 다른 플랫폼을 목표로 하지 않으면 보통 이 문제를 해결할 수 있다.나는 몇몇 컴파일러들이 당신에게 포장 지시서도 사용할 수 있다고 믿는다.
2.의 좋은 예는 COM에서 광범위하게 사용되는 변형 유형에서 찾을 수 있다.
엄밀히 말하면 그것은 정의되지 않았지만 실제로는 대부분의 컴파일러들은 그것을 정확히 a를 사용하는 것과 동일하게 취급한다.reinterpret_cast
한 유형에서 다른 유형으로, 그 결과는 구현이 정의된다.나는 너의 현재 코드 때문에 잠을 자지 않을 것이다.
실제 유니언 사용에 대한 한 가지 더 예로 CORBA 프레임워크는 태그가 지정된 유니언 접근방식을 사용하여 객체를 직렬화한다.모든 사용자 정의 클래스는 하나의 (거대) 유니언의 멤버로, 정수 식별자는 디마르할러에게 유니언을 해석하는 방법을 알려준다.
@보보보보 코드는 @joshua가 지적한 바와 같이 정확하다(슬프게도 나는 코멘트를 추가할 수 없으니 여기서 하는 것, 애초에 그것을 허용하지 않는 IMO의 잘못된 결정).
아마존닷컴은 적어도 C++14 이후로는 그렇게 해도 괜찮다고 말한다.
비조합 클래스 T1 유형의 활성 멤버가 있는 표준 레이아웃 유니언에서, m이 T1과 T2의 공통 초기 시퀀스의 일부인 경우(비휘발성 glvalue를 통한 휘발성 멤버의 판독이 정의되지 않은 것은 제외)에 따라 비조합 클래스 T2 유형의 다른 조합 멤버의 비정적 데이터 멤버 m을 판독할 수 있다.
T1과 T2는 어쨌든 같은 타입을 기부하기 때문에.
참조URL: https://stackoverflow.com/questions/2310483/purpose-of-unions-in-c-and-c
'programing' 카테고리의 다른 글
하위 디렉터리의 Vuex 액세스 모듈 (0) | 2022.04.25 |
---|---|
최종 블록은 항상 Java에서 실행되는가? (0) | 2022.04.25 |
v-select 또는 v-comobox에서 "모두 선택" 옵션을 사용하는 방법 (0) | 2022.04.25 |
Axios 인터셉터 - vuex 스토어에서 응답을 반환하는 방법 (0) | 2022.04.25 |
Java에서 null 문자열을 연결 중 (0) | 2022.04.25 |