programing

비트 와이즈 연산으로 인해 예기치 않은 변수 크기가 발생함

subpage 2023. 10. 21. 10:28
반응형

비트 와이즈 연산으로 인해 예기치 않은 변수 크기가 발생함

맥락

우리는 PIC 마이크로컨트롤러용 8비트 C 컴파일러를 사용하여 원래 컴파일된 C 코드를 포팅합니다.부호 없는 전역 변수(예: 오류 카운터)가 0으로 롤오버되는 것을 방지하기 위해 사용된 일반적인 관용구는 다음과 같습니다.

if(~counter) counter++;

여기서 비트 와이즈 연산자는 모든 비트를 반전시키고 문장은 다음의 경우에만 참입니다.counter최대값보다 작습니다.중요한 것은 이것이 변수 크기에 상관없이 작동한다는 것입니다.

문제

우리는 이제 GCC를 사용하는 32비트 ARM 프로세서를 목표로 하고 있습니다.우리는 동일한 코드가 다른 결과를 낳는다는 것을 알아냈습니다.우리가 알 수 있는 한, 비트 단위의 보조사 연산은 우리가 예상했던 것과 다른 크기의 값을 반환하는 것으로 보입니다.이를 재현하기 위해 GCC로 컴파일합니다.

uint8_t i = 0;
int sz;

sz = sizeof(i);
printf("Size of variable: %d\n", sz); // Size of variable: 1

sz = sizeof(~i);
printf("Size of result: %d\n", sz); // Size of result: 4

첫 번째 출력 라인에서는 다음과 같은 결과를 얻을 수 있습니다.i1바이트 입니다.그러나, 약간의 보완이 있습니다.i는 실제로 4바이트이며, 이를 지금과 비교해도 예상한 결과를 얻을 수 없기 때문에 문제를 야기합니다.예를 들어, 다음과 같은 경우(where)i적절히 initial화된 것입니다.uint8_t):

if(~i) i++;

알게될 거야.iFF에서 0x00으로 "랩 어라운드"합니다.이 동작은 이전 컴파일러 및 8비트 PIC 마이크로컨트롤러에서 의도한 대로 작동했던 때와 비교해 GCC에서 다릅니다.

우리는 다음과 같이 캐스팅을 통해 이 문제를 해결할 수 있음을 알고 있습니다.

if((uint8_t)~i) i++;

아니면, 에 의해

if(i < 0xFF) i++;

그러나 이 두 가지 해결 방법 모두에서 변수의 크기를 알아야 하며 소프트웨어 개발자에게 오류가 발생하기 쉽습니다.이러한 종류의 상한 검사는 코드베이스 전체에서 발생합니다.여러 크기의 변수가 있습니다(예:uint16_t그리고.unsigned char(등) 그리고 그렇지 않으면 작동하는 코드베이스에서 이러한 것들을 변경하는 것은 우리가 기대하는 바가 아닙니다.

질문.

문제에 대한 우리의 이해가 정확한가요? 그리고 이 관용구를 사용한 각 사례를 다시 방문할 필요가 없는 문제를 해결할 수 있는 옵션이 있습니까?비트와 같은 연산은 피연산자와 동일한 크기의 결과를 반환해야 한다는 우리의 가정이 맞습니까?프로세서 아키텍처에 따라 이 문제가 해결될 것으로 보입니다.나는 미친 약을 먹는 것 같고 C는 이것보다 좀 더 휴대성이 있어야 한다고 생각합니다.다시 말하지만, 이에 대한 우리의 이해는 틀릴 수 있습니다.

On the surface this might not seem like a huge issue but this previously-working idiom is used in hundreds of locations and we're eager to understand this before proceeding with expensive changes.


Note: There is a seemingly similar but not exact duplicate question here: Bitwise operation on char gives 32 bit result

I didn't see the actual crux of the issue discussed there, namely, the result size of a bitwise complement being different than what's passed into the operator.

지금 보시는 것은 정수 프로모션의 결과입니다.식에 정수 값이 사용되는 대부분의 경우 값의 유형이 다음보다 작은 경우int그 가치는 로 승격됩니다.int. 이는 C 표준의 섹션 6.3.1.1p2에 문서화되어 있습니다.

다음은 어디에서나 사용될 수 있는 표현입니다.int아니면unsigned int사용할 수 있음

  • 정수 형식을 가진 개체 또는 식을 다음과 같이 지정합니다.int아니면unsigned int)의 정수 변환 순위가 다음의 순위보다 작거나 같습니다.int그리고.unsigned int.
  • 유형의 비트 필드_Bool,int ,서명한, or무기명 int'

만약에int원래 유형의 모든 값을 나타낼 수 있습니다(비트 필드의 경우 너비에 따라 제한됨). 값은int; 그렇지 않으면, 그것은 a로 변환됩니다.unsigned int. 이런 것들을 정수승진이라고 합니다.다른 모든 유형은 정수 프로모션에 의해 변경되지 않습니다.

그래서 변수가 다음과 같은 유형을 가지면uint8_t값 255는 캐스트나 할당 이외의 연산자를 사용하면 먼저 유형으로 변환됩니다.int작업을 수행하기 전에 255의 값을 사용합니다.그래서.sizeof(~i)1이 아니라 4개를 줍니다.

섹션 6.5.3.3은 정수 승격이 다음에 적용된다고 설명합니다.~연산자:

결과는.~operator는 해당 (promoted) 피연산자의 비트 단위 상보입니다(즉, 변환된 피연산자의 해당 비트가 설정되지 않은 경우에만 결과의 각 비트가 설정됨).피연산자에서 정수 승격이 수행되며, 결과는 승격 유형을 갖습니다.승격된 유형이 서명되지 않은 유형인 경우 식~E해당 유형에서 나타내는 최대값 - 를 뺀 값과 같습니다.E.

그래서 32비트를 가정하면int,한다면counter8비트 값을 갖습니다.0xff32비트 값으로 변환됩니다.0x000000ff, 그리고 적용하는 것~당신에게 주는 것은0xffffff00.

아마도 이를 처리하는 가장 간단한 방법은 유형을 알 필요 없이 값이 증가한 후 0인지 확인하고 감소한 경우 확인하는 것일 것입니다.

if (!++counter) counter--;

부호가 없는 정수의 반올림은 양방향으로 작동하므로 0의 값을 줄이면 가장 큰 양의 값을 얻을 수 있습니다.

(i)의 크기; 변수 i의 크기를 요청하므로 1

(~i)의 크기로; 당신은 당신의 경우에 int인 식의 종류의 크기를 요청합니다 4.


사용방법

만약(~i)

가 255 값을 갖지 않는지(uint8_t의 경우) 알기 위해서는, 그냥 하기만 하면 됩니다.

if (i != 255)

그리고 당신은 휴대가 가능하고 읽을 수 있는 코드를 갖게 될 것입니다.


변수의 크기는 여러 가지입니다(예: unint16_t, unsigned char 등).

서명되지 않은 크기를 관리하려면:

if (i != (((uintmax_t) 2 << (sizeof(i)*CHAR_BIT-1)) - 1))

식이 일정하므로 컴파일 시 계산됩니다.

#include <limits.h>CHAR_B의 경우IT와 #는 <stdint>를 포함합니다.h>uintmax_t의 경우

다음은 "Add 1 to" 구현을 위한 몇 가지 옵션입니다.x하지만 대표적인 최대 값에 고정할 수 있습니다."라고 가정합니다.x는 부호가 없는 정수 타입입니다.

  1. 경우에만 하나 추가x는 해당 유형에서 나타낼 수 있는 최대값보다 작습니다.

    x += x < Maximum(x);
    

    의 정의는 다음 항목을 참조하십시오.Maximum이 에 의해 될 가능성이 이 방법은 컴파일러에 의해 비교, 조건부 집합 또는 이동, 추가와 같은 효율적인 명령에 최적화될 가능성이 높습니다.

  2. 유형의 가장 큰 값과 비교:

    if (x < ((uintmax_t) 2u << sizeof x * CHAR_BIT - 1) - 1) ++x
    

    (이것은 2를N 계산하며, 여기서 N은 비트 수를 나타냅니다.x, 2비트씩 N-1비트씩 쉬프트합니다.우리는 C 표준에 의해 정의되지 않기 때문에 1 N 비트를 이동하는 대신에 이것을 합니다.CHAR_BITmacro는 일부에게는 생소할 수 있습니다. 그것은 바이트의 비트 수이기 때문에,sizeof x * CHAR_BIT는 유형의 비트 수입니다.x.)

    심미성과 선명성을 위해 원하는 대로 매크로로 감쌀 수 있습니다.

    #define Maximum(x) (((uintmax_t) 2u << sizeof (x) * CHAR_BIT - 1) - 1)
    if (x < Maximum(x)) ++x;
    
  3. x0으로 감으면 수정합니다.if:

    if (!++x) --x; // !++x is true if ++x wraps to zero.
    
  4. x0으로 감으면 수정합니다. 식을 사용하여:

    ++x; x -= !x;
    

    이것은 명목상 분기가 없는 경우도 있지만(때로는 성능에 도움이 되는 경우도 있지만) 컴파일러는 필요한 경우 분기를 사용하여 위와 동일하게 구현할 수 있지만 대상 아키텍처에 적절한 명령이 있을 경우 무조건적인 명령을 사용할 수도 있습니다.

  5. 위의 매크로를 사용하는 브랜치리스 옵션은 다음과 같습니다.

    x += 1 - x/Maximum(x);
    

    한다면x는 그 유형의 최대치이며, 이는 다음과 같이 평가합니다.x += 1-1. 그렇지 않으면.x += 1-0 많은 그러나 많은 아키텍처에서는 분할이 다소 느립니다.컴파일러는 컴파일러와 대상 아키텍처에 따라 분할 없이 명령어에 이를 최적화할 수 있습니다.

stdint.h 이전의 변수 크기는 컴파일러마다 다를 수 있으며 C의 실제 변수 유형은 여전히 int, long 등이며 그 크기는 컴파일러 작성자에 의해 정의됩니다.일부 표준 또는 대상 특정 가정이 아닙니다.그런 다음 작성자는 두 월드를 매핑하기 위해 stdint.h를 만들어야 합니다. 이것이 uint_this를 int, long, short로 매핑하는 stdint.h의 목적입니다.

다른 컴파일러에서 코드를 포팅하고 char, short, int, long을 사용한다면 각각의 타입을 거쳐 직접 포팅을 해야 합니다.그리고 당신이 변수에 맞는 크기를 갖게 되든지, 선언은 변하지만 코드는 쓰여진 대로 작동합니다.

if(~counter) counter++;

또는...마스크 또는 타입캐스트를 직접 공급합니다.

if((~counter)&0xFF) counter++;
if((uint_8)(~counter)) counter++;

결국 이 코드가 작동하려면 새 플랫폼으로 포팅해야 합니다.방법에 대한 당신의 선택.네, 각 케이스에 맞는 시간을 들여서 제대로 해야 합니다. 그렇지 않으면 더 비싼 이 코드로 계속 돌아오게 됩니다.

포팅하기 전에 코드에서 변수 유형을 분리하고 변수 유형의 크기가 무엇인지 확인한 후, 이 작업을 수행하는 변수를 분리하고(grep하기 쉬워야 함), stdint.h 정의를 사용하여 선언을 변경하면 나중에 변경되지 않을 것입니다.그리고 당신은 놀랄것입니다 하지만 가끔 잘못된 머리말을 사용하기 때문에 밤에 잠을 더 잘 수 있도록 체크를 넣으시기도 합니다.

if(sizeof(uint_8)!=1) return(FAIL);

그리고 이러한 방식의 코딩이 가능하지만(~counter) counter++;를 사용하면 현재와 미래의 휴대성을 위해서는 마스크를 사용하여 크기를 특별히 제한하는 것이 가장 좋습니다(선언에 의존하지 않고). 코드가 처음부터 작성되었을 때 또는 포트를 완료한 후 다른 날 다시 포팅할 필요가 없을 것입니다.또는 코드를 더 잘 읽을 수 있도록 하기 위해, 만약 x<0xFF를 사용한다면, x!=0xFF 같은 것을 사용하면 컴파일러는 이 솔루션들에서 사용하는 것과 같은 코드로 코드를 최적화할 수 있습니다. 단지 코드를 더 잘 읽고 덜 위험하게 만들 뿐입니다.

제품이 얼마나 중요한지, 패치/업데이트를 발송하거나 트럭을 굴리거나 실험실로 걸어가 문제를 해결하기를 원하는 횟수에 따라 빠른 해결책을 찾는지, 아니면 영향을 받는 코드 라인을 만지기만 하면 되는지가 달라집니다.단지 100개 혹은 몇개의 항구가 그렇게 크지 않다면.

6.5.3.3
...
4 결과. ~operator는 해당 (promoted) 피연산자의 비트 단위 상보입니다(즉, 변환된 피연산자의 해당 비트가 설정되지 않은 경우에만 결과의 각 비트가 설정됨). 피연산자에서 정수 승격이 수행되며, 결과는 승격 유형을 갖습니다.승격된 유형이 서명되지 않은 유형인 경우 식 ~E해당 유형에서 나타내는 최대값 - 를 뺀 값과 같습니다. E.

C 2011 온라인 드래프트

문제는 그 피연산자가~로 승격됩니다.int작업자가 적용되기 전에.

유감스럽게도, 저는 이 문제에서 쉽게 벗어날 방법이 없다고 생각합니다.쓰기

if ( counter + 1 ) counter++;

승진도 거기에 적용되기 때문에 도움이 되지 않을 것입니다.제가 제안할 수 있는 유일한 방법은 개체가 나타낼 최대값에 대한 기호 상수를 생성하고 이에 대해 테스트하는 것입니다.

#define MAX_COUNTER 255
...
if ( counter < MAX_COUNTER-1 ) counter++;

언급URL : https://stackoverflow.com/questions/61232081/bitwise-operation-results-in-unexpected-variable-size

반응형