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


365

이 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 배열 대신 바이트 배열을 사용하는 것이 더 빠릅니다 . 그러나 광범위한 바이트 캐스트는 코드를 통해 확산되어 훨씬 더 읽을 수 없게 만듭니다.



10
여기서 유용한 표준에 대한 에릭의 지식은 아닙니다 . 언어 의 디자인 에 대한 그의 지식입니다 . 왜 안돼? 그러나 그렇습니다. Eric의 대답은 매우 결정적입니다. :)
Jon Skeet

143
아래의 다양한 musings는 디자인 고려 사항의 합리적인 근사치입니다. 더 일반적으로 : 나는 바이트를 "숫자"로 생각하지 않습니다. 나는 그것들을 숫자, 문자, 색상 또는 기타 로 해석 할 수있는 비트 패턴으로 생각합니다 . 수학을 계산하고 숫자로 취급하려는 경우 결과를 숫자로 더 일반적으로 해석되는 데이터 유형으로 이동하는 것이 좋습니다.
Eric Lippert 2016 년

28
@Eric : 바이트에는 의미가 있지만 short / ushort에는 의미가 없습니다.
Jon Skeet

23
@ 에릭 : byte1 | byte2전혀 숫자로 취급하지 않습니다. 이것은 비트 패턴으로 정확하게 취급합니다. 나는 당신의 관점을 이해하지만, C #에서 바이트에 대해 산술을 할 때마다 실제로는 숫자가 아닌 비트로 취급하고 있으며,이 동작은 항상 방해가됩니다.
Roman Starkov

답변:


228

코드 스 니펫의 세 번째 줄 :

byte z = x + y;

실제로 의미

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

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


아래 코드를 시도했지만 여전히 작동하지 않습니다. 바이트 z = (바이트) x + (바이트) y;
Anonymous

10
바이트에 대한 + 작업이 없기 때문입니다 (위 참조). 바이트 z = (byte) ((int) x + (int) y)
시도

35
이것은 가장 정확하고 간결한 답변이어야합니다. 바이트 사이에 추가, 그래서 대신 왜 작동 여부 ( "2 바이트를 추가"설명의 할 피연산자가 없습니다 그것은 결코 일어나지 않았다 ), 일어난 유일한 결과는 int이며 왜 명확하게 보여줍니다이기 때문에 2 개의 int의 추가 .
RichardTheKiwi

2
나는 다른 모든 답변을 읽는 현기증이 생겼습니다 (존 스케이트 씨에게 아무런 해가 없음). 이것은 후드 아래에서 일어나는 일을 정확하게 설명하는 가장 간단한 대답이라는 것을 알았습니다. 감사!
rayryeng

다음은이 컴파일러 기반의 자동 승진 할 때 식별하는 프로그램이 포함되어 내가 다른 곳에 쓴 대답 int발생하는됩니다 : stackoverflow.com/a/43578929/4561887는
가브리엘 스테이 플스

172

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

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

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

편집 : 짜증나게, 나는 이제 주석이 달린 ECMA C # 2 사양, 주석이 달린 MS C # 3 사양 및 주석 CLI 사양 을 살펴 보았으며, 내가 볼 수있는 한 아무도 언급 하지 않았습니다 . 난 확실히 내가 위의 이유를 본 적이 있지만 내가 어디 있는지 알고 있다면 난 불줄 해요. 사과, 참조 팬 :(


14
죄송 합니다만, 이것이 최선의 대답은 아닙니다.
VVS

42
최고의 답변이 아닌 것으로 판단되는 모든 답변을 다운 보증 했습니까 ? ;)
Jon Skeet

55
(명백히 말하면, 나는 실제로 당신에게 가려고하지는 않습니다. 모든 사람들이 자신의 다운 투표에 대한 자체 기준을 가지고있는 것 같습니다. 괜찮습니다. 나는 그것이 단지 비 이상적이지 않고 적극적으로 해롭다 고 믿는 경우에만 대답을 내리는 것입니다. )
Jon Skeet

21
나는 투표를 도구로 사용하여 상단에 "최고의"답변을 얻습니다. 실제로 나는 당신이 당신의 대답에 대해 전혀 말하지 않았다는 것을 알았습니다. 또 다른 이유는 귀하의 담당자가 투표에 대해 큰 보너스를 제공하고 귀하가 "더 나은"답변 위에 도착한다는 주관적인 느낌 일 수 있습니다.
VVS

23
IMO가 "최고의"답변을 얻는 가장 좋은 방법은 그것을 찬성하는 것입니다. 솔직히 말해서, 나는 여기에 가장 유익한 대답은 내가 생각하지 않는다가합니다 (이 "어떤 컴파일러의 일"관점 것은 반대) 디자인 관점에 대한 에릭의 질문에 코멘트 ...하지만 그 이외의 생각 입니다 많은 "성능"이상으로 대답하십시오. 특히, 나는 int + int = long을 암시하는 것처럼 "오버플로 방지"인수 (17 표)를 구입하지 않습니다.
Jon Skeet

68

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

'바이트'에 대한 작업이 '바이트'가 된 환상의 세계에 살았다 고 가정 해보십시오.

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

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

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


42
우리가 정수를 추가하고 오버플로하면 정수를 사용하여 다른 데이터 유형으로 자동 캐스트하지 않지만 바이트로 왜 그렇게합니까?
Ryan

2
int를 사용하면 오버플로가 발생합니다. int.MaxValue + 1 당신이 얻을 추가 시도 -2147483648 대신 2147483648
데이비드 Basarab에게

8
@ Longhorn213 : 그렇습니다. Ryan의 말은 : int math는 오버플로 할 수 있지만 int math는 오래 걸리지 않습니다.
Michael Petrotta 2016 년

28
바로 그거죠. 이것이 보안 조치 인 경우에는 매우 잘못 구현 된 것입니다.)
Jon Skeet

5
@Ryan : "lazy"는 C # 언어 디자이너에게 기본 수학만큼이나 기본적인 것에 대해 상당한 비용을 청구합니다. 그것들을 비난하고 싶다면 "C / C ++와의 호환성이 지나치게 높다"고하자.
Michael Petrotta 2016 년

58

씨#

ECMA-334에 따르면 추가는 int + int, uint + uint, long + long 및 ulong + ulong (ECMA-334 14.7.4)에서만 법적으로 정의됩니다. 따라서, 이들은 14.4.2와 관련하여 고려해야 할 후보 작업입니다. 바이트에서 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)


ECMA는 특정 작업 만 지정한다고해서 언어가 다른 규칙을 구현하지 못하게하지는 않습니다. VB.NET은 유용하게 수 byte3 = byte1 And byte2캐스트하지 않고 있지만, unhelpfully 경우 런타임 예외가 발생합니다 int1 = byte1 + byte2어떤 언어를 허용 할 경우 255 이상의 값을 모르겠어요 수율 byte3 = byte1+byte2경우 예외가 던져 그 255을 초과하는 경우 예외를 발생을하지만 int1 = byte1+byte2수율 256-510 범위의 값.
supercat

26

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

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

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

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


+1; 단지 이러한 인식은 잘못된 매 내가 지금까지 이동 한 시간과 논리합 C #에서 두 바이트 ... 아니었다면
로마 Starkov

결과를 잘라 내기위한 성능 비용이 없어야합니다. x86 어셈블리에서는 레지스터에서 1 바이트를 복사하거나 레지스터에서 4 바이트를 복사하는 것의 차이점입니다.
Jonathan Allen

1
@JonathanAllen 정확합니다. 확장 변환을 수행 할 때의 유일한 차이점은 아이러니하게도 충분 합니다. 현재 설계가 확대 명령을 실행하는 성능 저하를 초래 (중 연장하거나 확장 부호에 서명했다.)
reirab

" 바이트 유형이 무엇인지에 대한 인식 "- byte(및 char)에 대한이 동작을 설명 할 수 있지만 short의미 상 분명히 숫자가 아닙니다 .
smls

13

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

편집 : 찾았어요! 이 주제에 대한 훌륭한 정보는 여기에 있습니다 .


9

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

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

예:

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

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


23
그러나 더 큰 데이터 유형은 동일한 동작을 따르지 않습니다.
Inisheer 2016 년

12
오버플로 문제는 제쳐두고 있습니다. 논리를 가져 와서 언어에 적용하면 모든 데이터 유형은 덧셈 산술 후에 더 큰 데이터 유형을 반환합니다. int + int = int, long + long = long입니다. 나는 그 질문이 불일치에 관한 것이라고 생각한다.
Joseph

그것은 나의 첫 생각이지만 왜 int + int = long이 아닌가? 그래서 나는 "가능한 오버 플로우"논쟁을 사지 않고있다. 그러나 아직 <grin>.
Robert Cartaino 2016 년

11
아, 그리고 "가능한 오버 플로우"인수에 대해서는 왜 byte + byte = short가 아닌가?
Robert Cartaino 2016 년

A) C #의 규칙에 따라 왜 작동 방식으로 작동합니까? 아래 답변을 참조하십시오. B) 왜 그렇게 설계 되었습니까? 대부분의 사람들이 정수와 바이트를 사용하는 방식에 대한 주관적인 판단에 근거하여 아마도 사용성 고려 사항 일 것입니다.
mqp 2016 년

5

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

C # 언어 사양


4

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


3
스택에서 두 바이트를 모두 푸시 한 다음 "add"명령을 호출합니다. IL에서는 두 값을 "먹고"int로 바꿉니다.
Jonathan Allen

3

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

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


2

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

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

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

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

short a = 2, b = 3;

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

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

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


1

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


0

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


2
나는 한 번 다른 방법으로 귀찮게했다 :) 항상 바이트 결과가 필요한 것 같아서 항상 캐스팅해야합니다.
Roman Starkov

int로 캐스팅 할 필요가 없다는 것을 제외하고. 캐스트는 내재적입니다. 다른 방법 만 명시 적입니다.
Niki

1
@ nikie 나는 당신이 내 대답을 이해하지 못했다고 생각합니다. 두 바이트를 추가하면 바이트가 생성되는 경우 오버플로를 방지하기 위해 누군가는 추가하기 전에 피연산자 (결과가 아닌)를 int 로 캐스팅해야합니다 .
포트란

0

.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);


0

바이트와 ​​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 메모리 비용이 높고 더 빠릅니다 (할당 바이트가 적기 때문에)


-1

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

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

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

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


이것은 여전히 byte - bytereturns int, 또는 왜 캐스트 되지 않는지 설명하지 않습니다 short...
KthProg

덧셈이 뺄셈과 다른 유형을 반환하기를 원하는 이유는 무엇입니까? 경우 byte + byte수익률 int255 + 아무것도 바이트에 저장할 수있는 것보다 크기 때문에, 그것은 모든 바이트를 뺀 반환 유형의 일관성 측면에서의 int 이외의 다른 바이트 반환 아무것도 이해가되지 않습니다.
jrista

나는 위의 이유가 아마도 옳지 않다는 것을 보여줍니다. 결과에 "fitting"과 관련이 있으면 byte빼기는을 반환 byte하고 바이트 덧셈은 short( byte+ byte는 항상에 맞음)을 반환합니다 short. 당신이 말하는 것처럼 일관성에 관한 short것이라면 여전히 두 가지 작업 모두에 충분합니다 int. 분명히 이유가 혼합되어 있습니다. 모든 이유가 반드시 잘 생각 된 것은 아닙니다. 또는 아래의 성능 이유가 더 정확할 수 있습니다.
KthProg
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.