programing

C에서의 비트필드 조작

copysource 2022. 8. 17. 21:31
반응형

C에서의 비트필드 조작

C의 정수로 개별 비트를 테스트하고 설정하는 전형적인 문제는 아마도 가장 일반적인 중급 수준의 프로그래밍 기술 중 하나일 것입니다.다음과 같은 간단한 비트마스크를 사용하여 설정 및 테스트합니다.

unsigned int mask = 1<<11;

if (value & mask) {....} // Test for the bit
value |= mask;    // set the bit
value &= ~mask;   // clear the bit

흥미로운 블로그 게시물은 이것이 오류가 발생하기 쉽고, 유지보수가 어렵고, 잘못된 관행이라고 주장한다.C 언어 자체는 타입세이프하고 포터블한 비트레벨 액세스를 제공합니다.

typedef unsigned int boolean_t;
#define FALSE 0
#define TRUE !FALSE
typedef union {
        struct {
                boolean_t user:1;
                boolean_t zero:1;
                boolean_t force:1;
                int :28;                /* unused */
                boolean_t compat:1;     /* bit 31 */
        };
        int raw;
} flags_t;

int
create_object(flags_t flags)
{
        boolean_t is_compat = flags.compat;

        if (is_compat)
                flags.force = FALSE;

        if (flags.force) {
                [...]
        }
        [...]
}

하지만 이건 를 움츠러들게 한다.

동료와 내가 이것에 대해 나눴던 흥미로운 논쟁은 아직 해결되지 않았다.두 가지 스타일 모두 사용할 수 있으며, 기존의 비트 마스크 방식은 쉽고 안전하며 투명합니다.제 동료는 이것이 흔하고 쉽다는 것에 동의하지만, 비트필드 결합 방식은 휴대성과 안전을 위해 몇 개의 회선을 더 사용할 가치가 있습니다.

어느 쪽이든 더 이상 논쟁은 없을까?특히 비트마스크 방식은 놓칠 수 있지만 구조 방식은 안전한 곳에 엔디안니스와 같은 장애가 있을 수 있습니다.

비트필드는 "C는 기계어 내의 필드 순서를 보증하지 않는다"(C북)는 생각만큼 이식성이 높지 않습니다.

이것을 무시해도, 올바르게 사용한다면, 어느 쪽의 방법도 안전합니다.두 방법 모두 적분 변수에 대한 심볼 액세스를 허용합니다.비트필드 방식이 쓰기 더 쉽다고 주장할 수 있지만 검토해야 할 코드가 더 많다는 의미도 있습니다.

비트 설정과 클리어에 오류가 발생하기 쉬운 문제라면 올바르게 기능 또는 매크로를 쓰는 것이 좋습니다.

// off the top of my head
#define SET_BIT(val, bitIndex) val |= (1 << bitIndex)
#define CLEAR_BIT(val, bitIndex) val &= ~(1 << bitIndex)
#define TOGGLE_BIT(val, bitIndex) val ^= (1 << bitIndex)
#define BIT_IS_SET(val, bitIndex) (val & (1 << bitIndex)) 

따라서 val이 BIT_를 제외한 l값이어야 한다는 점에 개의치 않는다면 코드를 읽을 수 있습니다.IS_SET. 만족스럽지 않으면 할당을 꺼내 괄호로 묶은 후 val = SET_B로 사용합니다.IT(val, someIndex), 동등합니다.

실제로 답은 원하는 것과 원하는 방법을 분리하는 것을 고려하는 것입니다.

당신은 이것을 작가의 관점에서 생각해야 합니다. 당신의 청중을 알아야 합니다.그래서 고려해야 할 몇 가지 "감사"가 있습니다.

먼저, 전형적인 C 프로그래머가 있습니다. 그들은 평생 비트마스크를 했고 잠결에도 할 수 있었습니다.

두 번째는 신참인데, 이 모든 것이 무엇인지 전혀 모릅니다.그들은 마지막 직장에서 php를 프로그래밍하고 있었고, 지금은 당신을 위해 일하고 있습니다.(이것을 php를 하는 초보로서 말합니다)

첫 번째 사용자를 만족시키기 위해 글을 쓰면(하루 종일 비트마스크를 사용), 매우 기뻐하고, 코드를 눈을 가린 채로 유지할 수 있습니다.다만, 신참은 코드를 유지하려면 큰 학습 곡선을 극복해야 합니다.이진 연산자, 이러한 연산자를 사용하여 비트를 설정/삭제하는 방법 등에 대해 학습해야 합니다.이 기능을 발휘하기 위해 필요한 모든 요령을 신참이 도입하는 것은 거의 확실합니다.

한편, 만약 당신이 두 번째 독자를 만족시키기 위해 글을 쓴다면, 뉴브들은 코드를 유지하는 데 더 수월해질 것이다.더 쉽게 찾을 수 있을 거야

 flags.force = 0;

보다

 flags &= 0xFFFFFFFE;

첫 번째 관객은 심술궂게 굴지만, 그들이 새로운 구문을 고수할 수 없을 것이라고는 상상하기 어렵다.그냥 망치는 게 훨씬 더 힘들어.newb가 코드를 더 쉽게 유지 관리할 수 있기 때문에 새로운 버그는 발생하지 않습니다.옛날에는 안정적인 손과 자석 바늘이 필요했는데...비트마스크도 없었어요.(XKCD 고마워요)

따라서 코드를 newb-safe 하려면 비트마스크보다 필드를 사용하는 것이 좋습니다.

비트필드는 훌륭하고 읽기 쉽지만 불행히도 C언어는 메모리 내의 비트필드의 레이아웃을 지정하지 않기 때문에 온디스크 형식이나 바이너리 와이어 프로토콜로 패킹된 데이터를 처리하는 데 기본적으로 쓸모가 없습니다.이 결정은 C의 설계상의 오류입니다.리치는 주문을 선택하고 그대로 할 수 있었습니다.

첫 번째 방법이 더 좋습니다, IMHO.왜 문제를 애매하게 만드나요?약간 만지작거리는 것은 정말 기본적인 것이다.C는 잘했다.엔디안스는 중요하지 않아유니언 솔루션이 하는 일은 물건의 이름을 짓는 것뿐입니다.11은 수수께끼일 수 있지만 #defined to unomous name 또는 enum'으로 충분합니다.

"|&^~"와 같은 기본을 다루지 못하는 프로그래머들은 아마도 잘못된 일을 하고 있을 것이다.

유니언의 사용은 ANSI C 표준에 따라 동작을 정의하지 않았기 때문에 사용해서는 안 됩니다(적어도 이식 가능한 것으로 간주되지 않습니다).

ISO/IEC 9899:1999(C99) 표준에서:

Annex J - 휴대성에 관한 문제:

1 다음은 지정되지 않은 항목입니다.

: 구조체 또는 유니언에 값을 저장할 때의 패딩 바이트 값(6.2.6.1).

: (6.2.6.1)에 저장된 마지막 조합원 이외의 조합원 값.

6.2.6.1 - 언어 개념 - 유형 표현 - 일반:

6 값이 멤버 오브젝트를 포함한 구조체 또는 유니언 타입의 오브젝트에 저장되어 있는 경우, 패딩 바이트에 대응하는 오브젝트 표현의 바이트는 지정되지 않은 값을 취한다.[42]) 구조체 또는 결합 객체의 값이 트랩 표현일 수 있지만 구조체 또는 결합 객체의 값은 트랩 표현일 수 없습니다.

7 값이 유니언 타입의 오브젝트 멤버에 저장되어 있는 경우, 그 멤버에 대응하지 않지만 다른 멤버에 대응하고 있는 오브젝트 표현의 바이트는 지정되지 않은 값을 취한다.

따라서 비트필드↔정수 대응을 유지하고 휴대성을 유지하려면 링크된 블로그 투고와 달리 비트마스크링 방식을 사용하는 것이 좋습니다.

비트필드는 훌륭하지만 비트 조작 조작이 원자적이지 않기 때문에 멀티 스레드애플리케이션에서 문제가 발생할 수 있습니다.

예를 들어 다음과 같은 매크로가 있다고 가정할 수 있습니다.

#define SET_BIT(val, bitIndex) val |= (1 << bitIndex)

|=는 하나의 문이므로 원자 연산을 정의합니다.그러나 컴파일러에 의해 생성된 일반 코드는 = atomic을 만들려고 하지 않습니다.

따라서 여러 스레드가 서로 다른 set bit 동작을 실행하는 경우 set bit 동작 중 하나가 스플리어스일 수 있습니다.두 스레드가 모두 실행되므로:

  thread 1             thread 2
  LOAD field           LOAD field
  OR mask1             OR mask2
  STORE field          STORE field

결과는 field' = field OR mask1 OR mask2(의도)이거나, field' = field OR mask1(의도하지 않음)이거나, field' = field OR mask2(의도하지 않음)일 수 있습니다.

비트필드 접근법의 어떤 점이 당신을 움츠러들게 하나요?

두 기술 모두 적절한 위치에 있으며, 제가 결정할 수 있는 유일한 방법은 다음 중 하나를 사용하는 것입니다.

간단한 "일회성" 비트 조작을 위해 비트 연산자를 직접 사용합니다.

하드웨어 레지스터 맵과 같이 더 복잡한 경우에는 비트필드 접근 방식이 쉽게 유리합니다.

  • 비트필드는 보다 간결하게 사용할 수 있습니다(쓰기에는 /slightly/더 많은 장황함을 희생합니다).
  • 비트필드는 더 견고합니다(어쨌든 'int' 사이즈는 어느 정도인지).
  • 비트필드는 보통 비트 연산자만큼 빠릅니다.
  • 비트필드는 단일 비트필드와 여러 비트필드가 혼재되어 있는 경우 매우 강력하며 멀티비트필드를 추출하려면 수동 시프트가 필요합니다.
  • 비트필드는 효과적으로 자기 문서화되어 있습니다.구조를 정의하고 요소의 이름을 붙임으로써, 나는 그것이 무엇을 의미하는지 안다.
  • 또한 비트필드는 단일 int보다 큰 구조를 심리스하게 처리합니다.
  • 비트 연산자의 경우, 일반적인(나쁜) 관행은 비트 마스크에 대한 #정의입니다.

  • 비트필드에 관한 유일한 경고는 컴파일러가 오브젝트를 원하는 크기로 압축했는지 확인하는 것입니다.이것이 표준으로 정의되어 있는지 기억할 수 없기 때문에 assert(sizeof(myStruct)== N)가 도움이 됩니다.

어느 쪽이든 비트필드는 GNU 소프트웨어에서 수십 년 동안 사용되어 왔고, 그들에게 아무런 해를 끼치지 않았습니다.나는 기능들에 대한 매개 변수로서 그것들을 좋아한다.

나는 비트필드가 구조와는 반대로 재래식이라고 주장한다.모든 사람이 다양한 옵션을 설정하는 방법을 알고 있으며 컴파일러는 이를 CPU의 매우 효율적인 비트 조작으로 요약합니다.

마스크와 테스트를 올바르게 사용하면 컴파일러가 제공하는 추상화 기능을 통해 견고하고 심플하며 읽기 쉽고 깔끔하게 만들 수 있습니다.

온/오프 스위치가 필요한 경우 C에서 계속 사용합니다.

당신이 언급하고 있는 블로그 투고에는raw비트 필드의 대체 액세스 방식으로서 union 필드를 지정합니다.

가 사용한 raw는 문제 없습니다만, 그 외의 용도로 사용할 계획(비트 필드의 시리얼화, 개별 비트의 설정/체크 등)이라면, 재해가 코앞에 닥칠 것입니다.메모리의 비트 순서는 아키텍처에 의존하며 메모리 패딩 규칙은 컴파일러마다 다르므로(위키피디아 참조) 각 비트필드의 정확한 위치는 다를 수 있습니다.즉, 어떤 비트인지 알 수 없습니다.raw각 비트필드는 에 대응합니다.

하지만 섞을 계획이 없다면 먹는 게 좋을 거야raw밖으로 나가면 안전할 거야

Well you can't go wrong with structure mapping since both fields are accessable they can be used interchangably.

One benefit for bit fields is that you can easily aggregate options:

mask = USER|FORCE|ZERO|COMPAT;

vs

flags.user = true;
flags.force = true;
flags.zero = true;
flags.compat = true;

In some environments such as dealing with protocol options it can get quite old having to individually set options or use multiple parameters to ferry intermediate states to effect a final outcome.

But sometimes setting flag.blah and having the list popup in your IDE is great especially if your like me and can't remember the name of the flag you want to set without constantly referencing the list.

I personally will sometimes shy away from declaring boolean types because at some point I'll end up with the mistaken impression that the field I just toggled was not dependent (Think multi-thread concurrency) on the r/w status of other "seemingly" unrelated fields which happen to share the same 32-bit word.

My vote is that it depends on the context of the situation and in some cases both approaches may work out great.

C++에서는 그냥 사용하세요.std::bitset<N>.

오류가 발생하기 쉽습니다.이런 종류의 코드에는 많은 오류가 있습니다. 왜냐하면 어떤 사람들은 코드와 비즈니스 논리를 완전히 흐트러뜨려야 한다고 느끼기 때문입니다. 유지보수의 악몽을 만들어 냅니다.그들은 "진짜" 프로그래머가 글을 쓸 수 있다고 생각한다.value |= mask;,value &= ~mask;더 안 좋은 일이 생길 수도 있고, 그건 괜찮아요.주변에 몇 개의 증분 연산자가 있다면 더 좋을 것입니다.memcpy의, 포인터 캐스트 및 불분명하고 오류가 발생하기 쉬운 구문이 그 때 생각납니다.물론 일관성이 있을 필요는 없으며 두세 가지 방법으로 랜덤하게 비트를 플립할 수 있습니다.

My advice would be:

  1. 클래스 내에서 다음과 같은 메서드를 사용하여 이 ----를 캡슐화합니다.SetBit(...)그리고.ClearBit(...)(C에 클래스가 없는 경우 모듈).그러는 동안, 그들의 행동을 모두 기록할 수 있습니다.
  2. Unit test that class or module.

When I google for "c operators"

The first three pages are:

http://en.wikipedia.org/wiki/Operators_in_C_and_C%2B%2B http://h30097.www3.hp.com/docs/base_doc/DOCUMENTATION/V40F_HTML/AQTLTBTE/DOCU_059.HTM http://www.cs.mun.ca/~michael/c/op.http://http://www.cs.mun.ca/

..그래서 나는 언어를 처음 접하는 사람들에 대한 주장은 좀 바보같다고 생각한다.

거의 항상 비트 마스크와 함께 논리 연산을 직접 또는 매크로로 사용합니다.

 #define  ASSERT_GPS_RESET()                    { P1OUT &= ~GPS_RESET ; }

덧붙여서, 원래의 질문에서 당신의 유니언 정의는 제 프로세서/프로세서 조합에서는 동작하지 않습니다.int 타입의 폭은 16비트, 비트필드 정의는 32비트입니다.조금 더 휴대성을 높이려면 새로운 32비트 타입을 정의해야 합니다.이 타입은 포팅 연습의 일환으로 각 타깃아키텍처의 필수 베이스 타입에 매핑할 수 있습니다.저 같은 경우에는

typedef   unsigned long int     uint32_t

원래의 예에서는

typedef unsigned int uint32_t

typedef union {
        struct {
                boolean_t user:1;
                boolean_t zero:1;
                boolean_t force:1;
                int :28;                /* unused */
                boolean_t compat:1;     /* bit 31 */
        };
        uint32_t raw;
} flags_t;

중첩된 int도 부호 없이 만들어야 합니다.

음, 그렇게 하는 것도 한 방법일 것 같은데, 난 항상 단순하게 하는 게 더 좋아.

일단 익숙해지면, 마스크를 사용하는 것은 간단하고 모호하지 않고 휴대하기 쉽다.

비트필드는 간단하지만 추가 작업을 수행하지 않으면 이식할 수 없습니다.

MISRA 준거 코드를 기술할 필요가 있는 경우는, 정의되지 않은 동작이나 실장에 의존하는 동작을 피하기 위해서, MISRA 가이드 라인에 의해서 비트 필드, 유니언, 및 그 외의 많은 C 의 측면에 주의해 주세요.

일반적으로 읽고 이해하기 쉬운 것이 유지보수가 용이한 것입니다.C를 처음 접하는 동료가 있다면, 아마 "안전한" 접근방식이 그들이 이해하기 더 쉬울 것입니다.

저는 두 가지 점을 강조하는 것 외에는 이미 언급된 내용에 대해 별로 추가하지 않았습니다.

컴파일러는 비트필드 내의 비트를 원하는 방식으로 자유롭게 배열할 수 있습니다.즉, 마이크로컨트롤러 레지스터의 비트를 조작하려고 하는 경우나, 비트를 다른 프로세서(또는 다른 컴파일러가 있는 같은 프로세서)로 송신하는 경우는, 반드시 비트 마스크를 사용할 필요가 있습니다.

한편, 1개의 프로세서내에서 사용하기 위해서 비트와 작은 정수의 콤팩트한 표현을 작성하려고 하면, 비트필드는 유지보수가 용이하기 때문에 에러가 발생하기 쉬워집니다.또, 대부분의 컴파일러에서는, 적어도 수동으로 마스킹이나 시프트 하는 것 만큼은 효율적입니다.

언급URL : https://stackoverflow.com/questions/1044654/bitfield-manipulation-in-c

반응형