program tip

서명에 대한 것과 같은 C ++에 엔디안 수정자가없는 이유는 무엇입니까?

radiobox 2020. 12. 12. 10:24
반응형

서명에 대한 것과 같은 C ++에 엔디안 수정자가없는 이유는 무엇입니까?


(이 질문이 많은 유형의 언어에 적용될 수 있다고 생각하지만 C ++를 예로 선택했습니다.)

그냥 쓸 방법이없는 이유 :

struct foo {
    little int x;   // little-endian
    big long int y; // big-endian
    short z;        // native endianness
};

특정 멤버, 변수 및 매개 변수에 대한 엔디안을 지정하려면?

부호와 비교

나는 변수의 유형이 값을 저장하는 데 사용되는 바이트 수뿐만 아니라 계산을 수행 할 때 해당 바이트가 해석되는 방식을 결정한다는 것을 이해합니다.

예를 들어, 다음 두 선언은 각각 1 바이트를 할당하고 두 바이트 모두에 대해 가능한 모든 8 비트 시퀀스가 ​​유효한 값입니다.

signed char s;
unsigned char u;

그러나 동일한 이진 시퀀스는 다르게 해석 될 수 있습니다. 예를 들어 11111111할당 된 경우 -1을 의미하고 할당 된 s경우 255 를 의미 합니다 u. 부호있는 변수와 부호없는 변수가 동일한 계산에 포함되면 컴파일러 (대부분)가 적절한 변환을 처리합니다.

내 이해에 따르면 엔디안은 동일한 원리의 변형 일뿐입니다. 즉, 저장 될 메모리에 대한 컴파일 시간 정보를 기반으로 바이너리 패턴을 다르게 해석하는 것입니다.

저수준 프로그래밍을 허용하는 형식화 된 언어에 해당 기능이있는 것이 분명해 보입니다. 그러나 이것은 C, C ++ 또는 내가 아는 다른 언어의 일부가 아니며 온라인에서 이에 대한 논의를 찾지 못했습니다.

최신 정보

질문 한 후 처음 한 시간 동안받은 많은 댓글 중 몇 가지를 요약하려고합니다.

  1. 부호는 엄격하게 이진 (부호 또는 비 부호)이며, 두 개의 잘 알려진 변형 (크고 작은 변형)이 있지만 혼합 / 중간 엔디안과 같이 덜 알려진 변형도있는 엔디안과는 달리 항상 존재합니다. 새로운 변종은 미래에 발명 될 수 있습니다.
  2. 엔디안은 다중 바이트 값에 바이트 단위로 액세스 할 때 중요합니다. 멀티 바이트 구조의 메모리 레이아웃에 영향을 미치는 엔디안 외에도 많은 측면이 있으므로 이러한 종류의 액세스는 대부분 권장되지 않습니다.
  3. C ++는 추상 머신 을 대상으로 하고 구현에 대한 가정의 수를 최소화 하는 것을 목표로 합니다. 이 추상 기계는없는 어떤 엔디 언을.

또한 이제 저는 서명과 엔디안이 완벽한 비유가 아니라는 것을 깨달았습니다.

  • 엔디안은 무언가가 이진 시퀀스로 표현되는 방식 만을 정의 하지만 이제는 무엇을 표현할 있는지 정의 합니다 . big int둘 다 little int정확히 동일한 값 범위를 갖습니다.
  • 부호는 비트와 실제 값이 서로 매핑되는 방식을 정의 하지만 표현할 수있는 것에 영향을줍니다 . 예를 들어 -3은으로 표현할 수없고 unsigned char( char8 비트 라고 가정하면 ) 130은 signed char.

따라서 일부 변수의 엔디안을 변경하면 프로그램의 동작이 변경되지 않는 반면 (바이트 단위 액세스 제외) 부호 변경은 일반적으로 변경됩니다.


표준이 말하는 것

[intro.abstract]/1:

이 문서의 의미 론적 설명은 매개 변수화 된 비 결정적 추상 기계를 정의합니다. 이 문서는 준수 구현의 구조에 대한 요구 사항을 지정하지 않습니다. 특히 추상 기계의 구조를 복사하거나 에뮬레이트 할 필요가 없습니다. 오히려, 아래에 설명 된대로 추상 기계의 관찰 가능한 동작을 에뮬레이트하기 위해서는 준수 구현이 필요합니다.

C ++에는 엔디안 개념이 없기 때문에 엔디안 한정자를 정의 할 수 없습니다.

토론

Signness와 Endianness의 차이점에 대해 OP는 다음과 같이 썼습니다.

내 이해에 따르면 엔디안은 동일한 원칙 [(signness)]의 변형 일뿐입니다. 이진 패턴이 저장 될 메모리에 대한 컴파일 시간 정보를 기반으로 한 다른 해석입니다.

나는 signness가 의미 론적 측면과 대표적인 측면을 모두 가지고 있다고 주장합니다 1 . 어떤 [intro.abstract]/1의미에만 관심이 C ++입니다 의미 , 결코 서명 숫자가 메모리에 표현되어야하는 방법으로 해결할 수없는 2 . 실제로 "부호 비트" 는 C ++ 사양에서 한 번만 나타나며 구현 정의 값을 참조합니다.
반면에 엔디안은 대표적인 측면 만 가지고 있습니다. 엔디안은 의미를 전달하지 않습니다 .

C ++ 20에서는 std::endian나타납니다. 여전히 구현에 정의되어 있지만 정의되지 않은 동작을 기반으로 한 오래된 트릭 에 의존하지 않고 호스트의 엔디안을 테스트 해 보겠습니다 .


1) 의미 론적 측면 : 부호있는 정수는 0 미만의 값을 나타낼 수 있습니다. 대표적 측면 : 예를 들어 양 / 음 기호를 전달하기 위해 약간의 예약이 필요합니다.
2) 같은 맥락에서 C ++는 부동 소수점 숫자를 표현하는 방법을 설명하지 않습니다. IEEE-754가 자주 사용됩니다. 그러나 이것은 표준에 의해 시행되는 어떤 경우에도 구현에서 선택하는 것입니다. "The value representation of floating 포인트 유형은 구현에서 정의됩니다 . " .[basic.fundamental]/8


YSC의 답변 외에도 샘플 코드를 가져 와서 달성하려는 목표를 고려해 보겠습니다.

struct foo {
    little int x;   // little-endian
    big long int y; // big-endian
    short z;        // native endianness
};

이것이 아키텍처에 독립적 인 데이터 교환 (파일, 네트워크 등)을위한 레이아웃을 정확히 지정하기를 바랄 수 있습니다.

그러나 몇 가지가 아직 지정되지 않았기 때문에 작동하지 않을 수 있습니다.

  • 데이터 유형 크기 : 원하는 경우 little int32_t, big int64_tint16_t각각 을 사용해야 합니다.
  • 언어 내에서 엄격하게 제어 할 수없는 패딩 및 정렬 : use #pragma또는 __attribute__((packed))기타 컴파일러 관련 확장
  • 실제 형식 (1s 또는 2s 보완 부호, 부동 소수점 유형 레이아웃, 트랩 표현)

또는, 당신은 단순히 어떤 특정 하드웨어의 엔디안을 반영 할 수 있습니다 -하지만 big하고 little여기에 모든 가능성을 포함 (다만 가장 일반적인 두)하지 않습니다.

따라서 제안은 불완전하고 (모든 합리적인 바이트 순서 배열을 구별하지 못함) 비효율적이며 (목표를 달성하지 못함) 추가적인 단점이 있습니다.

  • 공연

    기본 바이트 순서에서 변수의 엔디안을 변경하면 산술, 비교 등 (하드웨어 가이 유형에서 올바르게 수행 할 수 없기 때문에)을 비활성화 하거나 더 많은 코드를 자동으로 주입하여 기본적으로 정렬 된 임시 작업을 만들어야합니다.

    여기서 논점은 네이티브 바이트 순서로 /에서 수동으로 변환하는 것이 더 빠르다 는 것이 아니라 명시 적으로 제어하면 불필요한 변환 수를 최소화하는 것이 더 쉬우 며 변환이 수행되는 경우보다 코드가 어떻게 작동하는지 추론하기가 훨씬 쉽다는 것입니다. 절대적인.

  • 복잡성

    정수 유형에 대해 오버로드되거나 특수화 된 모든 것은 이제 비 네이티브 엔디안 값이 전달되는 드문 이벤트에 대처하기 위해 두 배의 버전이 필요합니다. 그것이 단순히 포워딩 래퍼 (네이티브 순서로 /에서 변환하는 몇 가지 캐스트 포함) 일지라도 식별 할 수있는 이점이없는 많은 코드입니다.

이를 지원하기 위해 언어를 변경하는 것에 대한 마지막 주장은 코드에서 쉽게 할 수 있다는 것입니다. 언어 구문을 변경하는 것은 큰 문제이며 유형 래퍼와 같은 것에 비해 명백한 이점을 제공하지 않습니다.

// store T with reversed byte order
template <typename T>
class Reversed {
    T val_;
    static T reverse(T); // platform-specific implementation
public:
    explicit Reversed(T t) : val_(reverse(t)) {}
    Reversed(Reversed const &other) : val_(other.val_) {}
    // assignment, move, arithmetic, comparison etc. etc.
    operator T () const { return reverse(val_); }
};

정수 (수학적 개념)에는 양수와 음수의 개념이 있습니다. 이 추상적 인 기호 개념은 하드웨어에서 여러 가지 다른 구현을 가지고 있습니다.

Endianness is not a mathematical concept. Little-endian is a hardware implementation trick to improve the performance of multi-byte twos-complement integer arithmetic on a microprocessor with 16 or 32 bit registers and an 8-bit memory bus. Its creation required using the term big-endian to describe everything else that had the same byte-order in registers and in memory.

The C abstract machine includes the concept of signed and unsigned integers, without details -- without requiring twos-complement arithmetic, 8-bit bytes or how to store a binary number in memory.

PS: I agree that binary data compatibility on the net or in memory/storage is a PIA.


That's a good question and I have often thought something like this would be useful. However you need to remember that C aims for platform independence and endianness is only important when a structure like this is converted into some underlying memory layout. This conversion can happen when you cast a uint8_t buffer into an int for example. While an endianness modifier looks neat the programmer still needs to consider other platform differences such as int sizes and structure alignment and packing. For defensive programming when you want find grain control over how some variables or structures are represented in a memory buffer then it is best to code explicit conversion functions and then let the compiler optimiser generate the most efficient code for each supported platform.


Endianness is not inherently a part of a data type but rather of its storage layout.

As such, it would not be really akin to signed/unsigned but rather more like bit field widths in structs. Similar to those, they could be used for defining binary APIs.

So you'd have something like

int ip : big 32;

which would define both storage layout and integer size, leaving it to the compiler to do the best job of matching use of the field to its access. It's not obvious to me what the allowed declarations should be.


Short Answer: if it should not be possible to use objects in arithmetic expressions (with no overloaded operators) involving ints, then these objects should not be integer types. And there is no point in allowing addition and multiplication of big-endian and little-endian ints in the same expression.

Longer Answer:

As someone mentioned, endianness is processor-specific. Which really means that this is how numbers are represented when they are used as numbers in the machine language (as addresses and as operands/results of arithmetic operations).

The same is "sort of" true of signage. But not to the same degree. Conversion from language-semantic signage to processor-accepted signage is something that needs to be done to use numbers as numbers. Conversion from big-endian to little-endian and reverse is something that needs to be done to use numbers as data (send them over the network or represent metadata about data sent over the network such as payload lengths).

Having said that, this decision appears to be mostly driven by use cases. The flip side is that there is a good pragmatic reason to ignore certain use cases. The pragmatism arises out of the fact that endianness conversion is more expensive than most arithmetic operations.

If a language had semantics for keeping numbers as little-endian, it would allow developers to shoot themselves in the foot by forcing little-endianness of numbers in a program which does a lot of arithmetic. If developed on a little-endian machine, this enforcing of endianness would be a no-op. But when ported to a big-endian machine, there would a lot of unexpected slowdowns. And if the variables in question were used both for arithmetic and as network data, it would make the code completely non-portable.

Not having these endian semantics or forcing them to be explicitly compiler-specific forces the developers to go through the mental step of thinking of the numbers as being "read" or "written" to/from the network format. This would make the code which converts back and forth between network and host byte order, in the middle of arithmetic operations, cumbersome and less likely to be the preferred way of writing by a lazy developer.

And since development is a human endeavor, making bad choices uncomfortable is a Good Thing(TM).

Edit: here's an example of how this can go badly: Assume that little_endian_int32 and big_endian_int32 types are introduced. Then little_endian_int32(7) % big_endian_int32(5) is a constant expression. What is its result? Do the numbers get implicitly converted to the native format? If not, what is the type of the result? Worse yet, what is the value of the result (which in this case should probably be the same on every machine)?

Again, if multi-byte numbers are used as plain data, then char arrays are just as good. Even if they are "ports" (which are really lookup values into tables or their hashes), they are just sequences of bytes rather than integer types (on which one can do arithmetic).

Now if you limit the allowed arithmetic operations on explicitly-endian numbers to only those operations allowed for pointer types, then you might have a better case for predictability. Then myPort + 5 actually makes sense even if myPort is declared as something like little_endian_int16 on a big endian machine. Same for lastPortInRange - firstPortInRange + 1. If the arithmetic works as it does for pointer types, then this would do what you'd expect, but firstPort * 10000 would be illegal.

Then, of course, you get into the argument of whether the feature bloat is justified by any possible benefit.


From a pragmatic programmer perspective searching Stack Overflow, it's worth noting that the spirit of this question can be answered with a utility library. Boost has such a library:

http://www.boost.org/doc/libs/1_65_1/libs/endian/doc/index.html

The feature of the library most like the language feature under discussion is a set of arithmetic types such as big_int16_t.


Because nobody has proposed to add it to the standard, and/or because compiler implementer have never felt a need for it.

Maybe you could propose it to the committee. I do not think it is difficult to implement it in a compiler: compilers already propose fundamental types that are not fundamental types for the target machine.

The development of C++ is an affair of all C++ coders.

@Schimmel. Do not listen to people who justify the status quo! All the cited arguments to justify this absence are more than fragile. A student logician could find their inconsistence without knowing anything about computer science. Just propose it, and just don't care about pathological conservatives. (Advise: propose new types rather than a qualifier because the unsigned and signed keywords are considered mistakes).


Endianness is compiler specific as a result of being machine specific, not as a support mechanism for platform independence. The standard -- is an abstraction that has no regard for imposing rules that make things "easy" -- its task is to create similarity between compilers that allows the programmer to create "platform independence" for their code -- if they choose to do so.

처음에는 시장 점유율을 놓고 플랫폼간에 많은 경쟁이 있었고 컴파일러는 대부분 마이크로 프로세서 제조업체가 독점 도구로 작성하고 특정 하드웨어 플랫폼에서 운영 체제를 지원하기 위해 작성되었습니다. 인텔은 모토로라 마이크로 프로세서를 지원하는 컴파일러를 작성하는 데별로 관심이 없었을 것입니다.

C는 결국 Bell Labs에서 Unix를 다시 작성하기 위해 발명했습니다.

참고 URL : https://stackoverflow.com/questions/47531139/why-isnt-there-an-endianness-modifier-in-c-like-there-is-for-signedness

반응형