development

바이트 + 바이트 = int… 왜?

big-blog 2020. 2. 29. 15:24
반응형

바이트 + 바이트 = int… 왜?


이 C # 코드를 보면 :

byte x = 1;
byte y = 2;
byte z = x + y; // ERROR: Cannot implicitly convert type 'int' to 'byte'

byte(또는 short) 유형 에서 수행 된 모든 수학의 결과 는 암시 적으로 정수로 캐스트됩니다. 해결책은 명시 적으로 결과를 바이트로 다시 캐스팅하는 것입니다.

byte z = (byte)(x + y); // this works

내가 궁금한 것은 왜? 건축입니까? 철학적인가?

우리는 :

  • int+ int=int
  • long+ long=long
  • float+ float=float
  • double+ double=double

그래서 왜 안돼:

  • byte+ byte=byte
  • short+ short= short?

약간의 배경 : 나는 "작은 숫자"(즉, <8)에 대한 긴 계산 목록을 수행하고 중간 결과를 큰 배열로 저장합니다. (캐시 적중으로 인해 ) int 배열 대신 바이트 배열을 사용하는 것이 더 빠릅니다 . 그러나 광범위한 바이트 캐스트는 코드를 통해 확산되어 훨씬 더 읽을 수 없게 만듭니다.


코드 스 니펫의 세 번째 줄 :

byte z = x + y;

실제로 의미

byte z = (int) x + (int) y;

따라서 바이트에는 + 연산이 없으며 바이트는 먼저 정수로 캐스트되고 두 정수를 더한 결과는 (32 비트) 정수입니다.


"어떻게 발생 하는가"의 관점에서, 다른 사람들이 말했듯이 바이트, sbyte, short 또는 ushort로 산술하기 위해 C #에 의해 정의 된 연산자가 없기 때문입니다. 이 답변은 그 연산자가 정의되지 않았는 지에 관한 것입니다 .

나는 그것이 기본적으로 성능을 위해서라고 생각합니다. 프로세서는 32 비트로 매우 빠르게 산술 연산을 수행합니다. 결과에서 바이트로의 변환을 자동으로 수행 수는 있지만 실제로 해당 동작을 원하지 않는 경우 성능이 저하 될 있습니다.

나는 생각 이 주석 C #을 기준 중 하나에 언급되어있다. 보고있는 중 ...

편집 : 짜증나게, 나는 이제 주석이 달린 ECMA C # 2 사양, 주석이 달린 MS C # 3 사양 및 주석 CLI 사양 을 살펴 보았으며, 내가 볼 수있는 한 아무도 언급 하지 않았습니다 . 나는 위에 주어진 이유를 보았을 것이라고 확신 하지만, 내가 어디 있는지 알면 날아갑니다. 사과, 참조 팬 :(


나는 이것을 어딘가에 본 적이 있다고 생각 했다. 에서 이 문서 올드 뉴 살아라 :

'바이트'에 대한 작업이 '바이트'인 판타지 세계에 살았다 고 가정 해보십시오.

byte b = 32;
byte c = 240;
int i = b + c; // what is i?

이 환상의 세계에서 i의 가치는 16입니다! 왜? + 연산자에 대한 두 피연산자가 모두 바이트이므로 합계 "b + c"는 바이트로 계산되므로 정수 오버플로로 인해 16이됩니다. (앞서 언급했듯이 정수 오버플로는 새로운 보안 공격 벡터입니다.)

편집 : Raymond는 본질적으로 C와 C ++이 원래 취한 접근법을 방어하고 있습니다. 이 의견에서 그는 언어 역 호환성이라는 이유로 C #이 동일한 접근 방식을 취한다는 사실을 변호합니다.


씨#

ECMA-334에 따르면 추가는 int + int, uint + uint, long + long 및 ulong + ulong (ECMA-334 14.7.4)에서만 법적으로 정의됩니다. 따라서, 이들은 14.4.2와 관련하여 고려해야 할 후보 작업입니다. byte에서 int, uint, long 및 ulong으로 암시 적 캐스트가 있기 때문에 모든 추가 함수 멤버는 14.4.2.1에서 적용 가능한 함수 멤버입니다. 14.4.2.3의 규칙에 따라 최상의 암시 적 캐스트를 찾아야합니다.

int (T1)에 캐스팅 (C1)을 캐스팅 (C2)을 uint (T2) 또는 ulong (T2)에 캐스팅하는 것보다 낫습니다.

  • T1이 int이고 T2가 uint이거나 ulong이면 C1이 더 나은 변환입니다.

int에서 long으로의 암시 적 캐스트가 있기 때문에 (C1)에서 int (T1)로 캐스트하는 것이 낫습니다 (C2).

  • T1에서 T2 로의 암시 적 변환이 존재하고 T2에서 T1 로의 암시 적 변환이 존재하지 않으면 C1이 더 나은 변환입니다.

따라서 int + int 함수가 사용되어 int를 반환합니다.

이것이 C # 사양에 매우 깊게 묻혀 있다고 말하는 매우 긴 방법입니다.

CLI

CLI는 6 가지 유형 (int32, native int, int64, F, O 및 &)에서만 작동합니다. (ECMA-335 파티션 3 섹션 1.5)

바이트 (int8)는 이러한 유형 중 하나가 아니며 추가하기 전에 자동으로 int32로 강제 변환됩니다. (ECMA-335 파티션 3 섹션 1.6)


바이트를 추가하고 결과를 바이트로 다시 자르는 비 효율성을 나타내는 대답이 올바르지 않습니다. x86 프로세서에는 8 비트 수량의 정수 연산을 위해 특별히 설계된 명령어가 있습니다.

실제로 x86 / 64 프로세서의 경우 32 비트 또는 16 비트 작업을 수행하면 디코딩해야하는 피연산자 접두사 바이트로 인해 64 비트 또는 8 비트 작업보다 효율성이 떨어집니다. 32 비트 시스템에서 16 비트 작업을 수행하면 동일한 결과가 발생하지만 8 비트 작업을위한 전용 opcode가 여전히 있습니다.

많은 RISC 아키텍처에는 유사한 기본 단어 / 바이트 효율적인 명령어가 있습니다. 일반적으로 저장 및 변환에서 부호있는 값의 비트 길이를 갖지 않는 것.

다시 말해서,이 결정은 하드웨어의 비 효율성으로 인한 것이 아니라 바이트 유형이 무엇인지에 대한 인식을 기반으로해야합니다.


바이트가 실제로 + 연산자에 과부하가 걸리지 않는 방법에 대해 Jon Skeet에서 무언가를 읽은 것을 기억합니다 (지금 찾을 수 없으며 계속 볼 것입니다). 실제로 샘플에서와 같이 2 바이트를 추가 할 때 각 바이트는 실제로 암시 적으로 int로 변환됩니다. 그 결과는 분명히 int입니다. 이제 이것이 이런 식으로 설계된 이유에 관해서는 Jon Skeet 자신이 게시 할 때까지 기다립니다. :)

편집 : 찾았어요! 바로이 주제에 대한 다양한 정보를 원하시면 여기 .


이것은 오버플로와 캐리 때문입니다.

두 개의 8 비트 숫자를 추가하면 9 비트로 오버플로 될 수 있습니다.

예:

  1111 1111
+ 0000 0001
-----------
1 0000 0000

나는 확실히 모르겠지만, 나는 가정 ints, longs그리고 doubles그들이 그대로 꽤 크기 때문에 더 많은 공간이 제공됩니다. 또한 4의 배수로, 내부 데이터 버스의 너비가 4 바이트 또는 32 비트 (현재 64 비트가 널리 보급되고 있음)이기 때문에 컴퓨터에서보다 효율적으로 처리 할 수 ​​있습니다. 바이트와 ​​쇼트는 조금 더 비효율적이지만 공간을 절약 할 수 있습니다.


C # 언어 사양 1.6.7.5 7.2.6.2 이진 숫자 승격에서 두 피연산자가 다른 여러 범주에 맞지 않으면 두 피연산자를 모두 int로 변환합니다. 내 생각에 그들은 바이트를 매개 변수로 사용하기 위해 + 연산자를 오버로드하지 않았지만 다소 정상적으로 작동하기를 원하므로 int 데이터 유형을 사용합니다.

C # 언어 사양


내 의혹은 C #이 실제로 operator+정의 된 on int( 블록 int에 있지 않으면를 반환)을 호출 checked하고 암시 적으로 bytes/ shortsto를 모두 캐스팅한다는 것입니다 ints. 이것이 동작이 일치하지 않는 이유입니다.


이것은 아마도 언어 디자이너 측에서 실질적인 결정이었을 것입니다. 결국 int는 32 비트 부호있는 정수인 Int32입니다. int보다 작은 유형에서 정수 연산을 수행 할 때마다 대부분의 32 비트 CPU에 의해 32 비트 부호있는 int로 변환됩니다. 그것은 작은 정수가 넘칠 가능성과 결합하여 아마도 거래를 봉인했을 것입니다. 오버 플로우 / 언더 플로우를 지속적으로 확인하는 번거 로움을 덜고 바이트의 표현식의 최종 결과가 범위 내에있을 때 일부 중간 단계에서 범위를 벗어났다는 사실에도 불구하고 올바른 결과를 얻습니다. 결과.

또 다른 생각 : 이러한 유형의 오버 플로우 / 언더 플로우는 가장 가능성이 높은 대상 CPU에서 자연적으로 발생하지 않기 때문에 시뮬레이션해야합니다. 왜 귀찮게?


이것은 대부분이 주제와 관련된 내 대답이며, 여기 비슷한 질문에 먼저 제출 되었습니다 .

Int32보다 작은 정수를 갖는 모든 연산은 기본적으로 계산하기 전에 32 비트로 반올림됩니다. 결과가 Int32 인 이유는 단순히 계산 후에 그대로 두는 것입니다. MSIL 산술 opcode를 확인하면 작동하는 정수 유형은 Int32 및 Int64뿐입니다. "디자인 상"입니다.

결과를 Int16 형식으로 되돌리려면 코드로 캐스트를 수행하거나 컴파일러가 (전후 적으로) "후드"아래에서 변환을 내보내는 것은 무의미합니다.

예를 들어 Int16 산술을 수행하려면

short a = 2, b = 3;

short c = (short) (a + b);

이 두 숫자는 32 비트로 확장되고 추가 된 다음 16 비트로 잘려서 MS가 의도 한 방식입니다.

짧은 (또는 바이트) 사용의 이점은 주로 대량의 데이터 (그래픽 데이터, 스트리밍 등)가있는 경우 스토리지입니다.


바이트에 대한 추가가 정의되지 않았습니다. 그래서 그들은 추가를 위해 int로 캐스팅됩니다. 대부분의 수학 연산 및 바이트에 해당됩니다. (이것이 이전 언어로 사용되었던 방식입니다. 오늘 그것이 사실이라고 가정합니다).


나는 그것이 어떤 작업이 더 일반적인 지에 대한 디자인 결정이라고 생각합니다 ... byte + byte = byte 경우 int가 결과로 필요할 때 int로 캐스팅하여 훨씬 더 많은 사람들을 괴롭힐 것입니다.


.NET Framework 코드에서 :

// bytes
private static object AddByte(byte Left, byte Right)
{
    short num = (short) (Left + Right);
    if (num > 0xff)
    {
        return num;
    }
    return (byte) num;
}

// shorts (int16)
private static object AddInt16(short Left, short Right)
{
    int num = Left + Right;
    if ((num <= 0x7fff) && (num >= -32768))
    {
        return (short) num;
    }
    return num;
}

.NET 3.5 이상으로 단순화하십시오 .

public static class Extensions 
{
    public static byte Add(this byte a, byte b)
    {
        return (byte)(a + b);
    }
}

지금 당신은 할 수 있습니다 :

byte a = 1, b = 2, c;
c = a.Add(b);


바이트와 ​​int 사이의 성능을 테스트했습니다.
int 값으로 :

class Program
{
    private int a,b,c,d,e,f;

    public Program()
    {
        a = 1;
        b = 2;
        c = (a + b);
        d = (a - b);
        e = (b / a);
        f = (c * b);
    }

    static void Main(string[] args)
    {
        int max = 10000000;
        DateTime start = DateTime.Now;
        Program[] tab = new Program[max];

        for (int i = 0; i < max; i++)
        {
            tab[i] = new Program();
        }
        DateTime stop = DateTime.Now;

        Debug.WriteLine(stop.Subtract(start).TotalSeconds);
    }
}

바이트 값으로 :

class Program
{
    private byte a,b,c,d,e,f;

    public Program()
    {
        a = 1;
        b = 2;
        c = (byte)(a + b);
        d = (byte)(a - b);
        e = (byte)(b / a);
        f = (byte)(c * b);
    }

    static void Main(string[] args)
    {
        int max = 10000000;
        DateTime start = DateTime.Now;
        Program[] tab = new Program[max];

        for (int i = 0; i < max; i++)
        {
            tab[i] = new Program();
        }
        DateTime stop = DateTime.Now;

        Debug.WriteLine(stop.Subtract(start).TotalSeconds);
    }
}

결과 :
byte : 3.57s 157mo, 3.71s 171mo, 3.74s 168mo with CPU ~ = 30 %
int : 4.05s 298mo, 3.92s 278mo, 4.28 294mo with CPU 포함 ~ = 27 %
결론 :
byte는 CPU를 더 많이 사용하지만 les 메모리 비용이 높고 더 빠릅니다 (할당 바이트가 적기 때문에)


다른 모든 위대한 의견 외에도, 나는 작은 작은 음식을 추가 할 것이라고 생각했습니다. 많은 의견이 왜 int, long 및 기타 다른 숫자 유형 이이 규칙을 따르지 않는지 궁금해했습니다 ... 산술에 대한 응답으로 "더 큰"타입을 반환하십시오.

많은 대답은 성능과 관련이 있습니다 (32 비트는 8 비트보다 빠릅니다). 실제로 8 비트 숫자는 32 비트 CPU의 32 비트 숫자입니다 .... 2 바이트를 추가하더라도 CPU가 작동하는 데이터 청크는 32 비트가됩니다 ... 따라서 int 추가는 2 바이트를 추가하는 것보다 "빠른"것입니다. CPU와 동일합니다. 이제 두 개의 정수를 추가하면 32 비트 프로세서에 두 개의 정수를 추가하는 것보다 빠릅니다. 두 개의 정수를 추가하면 프로세서 워드보다 넓은 수의 작업을하기 때문에 더 많은 마이크로 옵스가 필요하기 때문입니다.

바이트 산술이 정수를 초래하는 근본적인 이유는 매우 명확하고 직선적이라고 생각합니다. 8 비트는 그리 멀지 않습니다! : D 8 비트의 경우 부호없는 범위는 0-255입니다. 그것은 작업 할 공간 많지 않습니다 ... 바이트 제한에 빠질 가능성은 산술에 사용할 때 매우 높습니다. 그러나 int, long 또는 double 등으로 작업 할 때 비트가 부족할 확률은 크게 낮아서 더 이상 필요하지 않습니다.

바이트 의 스케일이 너무 작기 때문에 byte에서 int 로의 자동 변환은 논리적 입니다. int에서 long으로, float에서 double로 등으로 자동 변환하는 것은 그 숫자의 규모가 크기 때문에 논리적이지 않습니다 .

참고 URL : https://stackoverflow.com/questions/941584/byte-byte-int-why



반응형