비트 필드에 값을 할당해도 동일한 값이 반환되지 않는 이유는 무엇입니까?


96

이 Quora 게시물 에서 아래 코드를 보았습니다 .

#include <stdio.h>

struct mystruct { int enabled:1; };
int main()
{
  struct mystruct s;
  s.enabled = 1;
  if(s.enabled == 1)
    printf("Is enabled\n"); // --> we think this to be printed
  else
    printf("Is disabled !!\n");
}

C 및 C ++ 모두에서 코드 출력이 예상치 못한 것입니다 .

비활성화되었습니다 !!

그 게시물에 "사인 비트"관련 설명이 나와 있지만, 어떻게 뭔가를 설정하고 그대로 반영하지 않는지 이해할 수 없습니다.

누군가 더 자세한 설명을 할 수 있습니까?


참고 : 두 태그 & 비트 필드를 설명하는 표준이 약간 다르기 때문에 필요합니다. C 사양C ++ 사양에 대한 답변을 참조하십시오 .


46
비트 필드가 선언되어 있기 때문에 int0-1.
Osiris

6
int가 -1을 저장하는 방법을 생각하십시오. 모든 비트는 1로 설정됩니다. 따라서 비트가 하나뿐이라면 분명히 -1이어야합니다. 따라서 1 비트 int의 1과 -1은 동일합니다. 체크를 'if (s.enabled! = 0)'로 변경하면 작동합니다. 0이 될 수 없기 때문입니다.
Jürgen

3
이러한 규칙은 C와 C ++에서 동일합니다. 그러나 태그 사용 정책 에 따라 C로만 태그를 지정하고 필요하지 않은 경우 교차 태그 지정을 자제해야합니다. C ++ 부분을 제거하겠습니다. 게시 된 답변에는 영향을주지 않습니다.
Lundin

8
로 변경해 보셨습니까 struct mystruct { unsigned int enabled:1; };?
ChatterOne

4
C 및 C ++ 태그 정책 , 특히 여기 에서 커뮤니티 합의를 통해 설정된 교차 태그 C 및 C ++에 관한 부분을 읽으 십시오 . 롤백 전쟁에 참여하지 않지만이 질문은 C ++ 태그가 잘못되었습니다. 다양한 TC로 인해 언어에 약간의 차이가 발생하더라도 C와 C ++의 차이점에 대해 별도의 질문을하십시오.
Lundin

답변:


78

비트 필드는 표준에 의해 엄청나게 잘못 정의되어 있습니다. 이 코드가 주어지면 struct mystruct {int enabled:1;};우리 수 없습니다 .

  • 이것이 차지하는 공간의 양-패딩 비트 / 바이트가 있고 메모리에있는 위치.
  • 비트가 메모리에있는 위치. 정의되지 않았으며 엔디안에 따라 다릅니다.
  • int:n비트 필드가 부호있는 것으로 간주 되는지 또는 부호없는 것으로 간주 되는지 여부 입니다.

마지막 부분과 관련하여 C17 6.7.2.1/10은 다음과 같이 말합니다.

비트 필드는 지정된 수의 비트 125 로 구성된 부호있는 또는 부호없는 정수 유형을 갖는 것으로 해석됩니다.

위 내용을 설명하는 비 규범 적 참고 :

125) 위의 6.7.2에 명시된 바와 같이, 사용 된 실제 유형 지정자가 int이거나으로 정의 된 typedef-name int이면 비트 필드가 서명되었는지 여부가 구현 정의됩니다.

비트 필드를로 간주 signed int하고 약간의 크기를 만들면 1부호 비트에 대해서만 데이터 공간이 없습니다. 이것이 프로그램이 일부 컴파일러에서 이상한 결과를 제공 할 수있는 이유입니다.

좋은 연습:

  • 어떤 목적으로도 비트 필드를 사용하지 마십시오.
  • int모든 형태의 비트 조작에 서명 된 유형을 사용하지 마십시오 .

5
작업에서 우리는 비트 필드의 크기와 주소에 대한 static_asserts를 사용하여 패딩되지 않도록합니다. 펌웨어의 하드웨어 레지스터에 비트 필드를 사용합니다.
Michael

4
@Lundin : # define-d 마스크 및 오프셋의 추악한 점은 코드가 시프트 및 비트 AND / OR 연산자로 흩어져 있다는 것입니다. 비트 필드를 사용하면 컴파일러가이를 처리합니다.
Michael

4
@Michael bitfields를 사용하면 컴파일러가이를 처리합니다. 글쎄, 당신의 표준이 "이동할 수 없다"고 "예측할 수 없다"면 괜찮다. 내 것이 그것보다 높습니다.
Andrew Henle

3
@AndrewHenle Leushenko는 C 표준 자체 의 관점 에서 x86-64 ABI를 따를 것인지 아닌지 여부는 구현에 달려 있다고 말합니다.
mtraceur

3
@AndrewHenle 맞아요, 두 점에 동의합니다. 내 요점은 Leushenko에 대한 귀하의 의견 불일치가 C 표준에 의해 엄격하게 정의되지 않았거나 플랫폼 ABI에 의해 엄격하게 정의되지 않은 것들만을 지칭하기 위해 "정의 된 구현"을 사용하고 있다는 사실로 귀결되는 것입니다. C 표준에 의해 엄격하게 정의되지 않은 모든 것.
mtraceur

58

나는 이해할 수 없습니다. 어떻게 우리가 무언가를 설정했는데 그것이 그대로 나타나지 않는 것이 가능합니다.

왜 컴파일되고 오류가 발생하는지 묻고 있습니까?

예, 이상적으로는 오류가 발생합니다. 컴파일러의 경고를 사용하면 그렇습니다. GCC에서 -Werror -Wall -pedantic:

main.cpp: In function 'int main()':
main.cpp:7:15: error: overflow in conversion from 'int' to 'signed char:1' 
changes value from '1' to '-1' [-Werror=overflow]
   s.enabled = 1;
           ^

이것이 구현 정의에 맡겨진 이유와 오류에 대한 이유는 캐스트가 필요한 이전 코드를 깨는 것을 의미하는 과거 사용과 더 관련이있을 수 있습니다. 이 표준의 저자는 경고가 우려되는 사람들에게 여유를주기에 충분하다고 생각할 수 있습니다.

규범주의를 도입하기 위해 @Lundin의 진술을 반영하겠습니다 . "어떤 목적으로도 비트 필드를 사용하지 마십시오." 처음에 비트 필드가 필요하다고 생각하게 만드는 메모리 레이아웃 세부 사항에 대해 저수준 및 구체적으로 얻을 수있는 좋은 이유가 있다면 거의 확실하게 가지고있는 다른 관련 요구 사항이 저사양에 맞서 실행될 것입니다.

(TL; DR-합법적으로 비트 필드가 "필요"할만큼 정교하다면 서비스를 제공 할만큼 충분히 정의되지 않은 것입니다.)


15
표준의 작성자는 비트 필드 챕터가 디자인 된 날 휴일에있었습니다. 그래서 관리인이해야했습니다. 에 대한 아무런 근거가 없다 아무것도 비트 필드가 설계 방법에 대한이.
Lundin

9
일관된 기술적 근거 가 없습니다 . 그러나 그것은 정치적 근거 가 있다는 결론을 내릴 수있게합니다. 즉, 기존 코드 나 구현을 부정확하게 만드는 것을 피하는 것입니다. 하지만 결과적으로 신뢰할 수있는 비트 필드가 거의 없습니다.
John Bollinger

6
@JohnBollinger C90에 많은 피해를 입힌 확실히 정치가있었습니다. 나는 한때 많은 쓰레기의 근원을 설명했던위원회 위원과 이야기를 나눴다. ISO 표준은 특정 기존 기술을 선호하는 것을 허용 할 수 없었다. 이것이 우리가 1의 보수 및 부호있는 크기에 대한 지원, 구현 정의 서명, char8 비트가 아닌 바이트에 대한 지원 등과 같은 모로 닉적인 것들에 갇혀있는 이유입니다 . 그들은 모로 닉 컴퓨터에 시장 불이익을 줄 수 없었습니다.
Lundin

1
@Lundin 트레이드 오프가 오류로 만들어 졌다고 믿었던 사람들의 글과 사후 분석 모음을 보는 것은 흥미로울 것입니다. 나는 이러한 "우리가 지난번에 그렇게했지만 잘되지 않았다"에 대한 연구가 사람들의 머릿속에있는 이야기 가 아닌 다음 사례를 알리는 제도적 지식 이되었는지 궁금합니다 .
HostileFork은 그나마 신뢰 SE 말한다

1
이것은 여전히 ​​포인트 번호로 나열됩니다. C2x 헌장에서 C의 원래 원칙 중 하나 : "기존 코드는 중요하지만 기존 구현은 중요하지 않습니다." ... "C를 정의하는 모범이되는 구현은 하나도 없었습니다. 기존의 모든 구현이 표준을 준수하기 위해 다소 변경되어야한다고 가정합니다."
Leushenko

23

이것은 구현 정의 동작입니다. 나는 당신이 이것을 실행하는 기계가 2s-compliment 부호있는 정수를 사용 int하고이 경우 if 문의 true 부분을 입력하지 않는 이유를 설명하기 위해 부호있는 정수로 취급한다고 가정하고 있습니다.

struct mystruct { int enabled:1; };

enable1 비트 비트 필드로 선언 합니다. 서명되었으므로 유효한 값은 -10입니다. 필드를 1오버플로하도록 설정하면 해당 비트가 다시 돌아갑니다 -1(정의되지 않은 동작).

부호 비트 필드를 처리 할 때 기본적으로 최대 값은 2^(bits - 1) - 1이다 0이 경우.


"서명 된 이후 유효한 값은 -1과 0"입니다. 누가 서명했다고 했습니까? 정의되지 않았지만 구현 정의 동작입니다. 서명 된 경우 유효한 값은 다음 -과 같습니다.+ 입니다. 2의 보수는 중요하지 않습니다.
Lundin

5
@Lundin 1 비트 투스 칭찬 번호에는 가능한 값이 두 개뿐입니다. 비트가 설정된 경우 부호 비트이므로 -1입니다. 이 설정되지 않는 경우, 0 내가 이것을 구현, 난 그냥 가장 일반적인 주입을 사용하여 결과를 설명하고있어 정의 알고 "긍정적"인
NathanOliver

1
여기서 핵심은 2의 보수 또는 다른 서명 된 형식이 사용 가능한 단일 비트로 작동 할 수 없다는 것입니다.
Lundin

1
@JohnBollinger 이해합니다. 이것이 구현이 정의되어 있다는 디스크 리머가있는 ​​이유입니다. 적어도 큰 3의 경우 그들은 모두이 int경우 서명 된 것으로 취급 합니다. 비트 필드가 그렇게 지정되지 않은 것은 부끄러운 일입니다. 기본적으로 여기에이 기능이 있습니다. 사용 방법은 컴파일러에 문의하십시오.
NathanOliver

1
@Lundin, 부호있는 정수 표현에 대한 표준 문구는 허용 된 세 가지 대안 중 적어도 두 개에서 0 값 비트가있는 경우를 완벽하게 처리 할 수 ​​있습니다. 이것은 알고리즘 해석을 제공하는 대신 부호 비트에 (음수) 자리 값 을 할당하기 때문에 작동합니다 .
John Bollinger

10

2의 보수 시스템에서 가장 왼쪽 비트가 부호 비트라고 생각할 수 있습니다. 따라서 가장 왼쪽 비트가 설정된 부호있는 정수는 음수 값입니다.

1 비트 부호있는 정수가 있으면 부호 비트 만 있습니다. 따라서 1해당 단일 비트에 할당 하면 부호 비트 만 설정할 수 있습니다. 따라서 다시 읽을 때 값은 음수로 해석되므로 -1입니다.

1 비트 부호있는 정수가 보유 할 수있는 값은 다음 -2^(n-1)= -2^(1-1)= -2^0= -1과 같습니다.2^n-1= 2^1-1=0


8

당으로 C ++ 표준 n4713 , 매우 유사한 코드가 제공됩니다. 사용되는 유형은 BOOL(사용자 정의)이지만 모든 유형에 적용 할 수 있습니다.

12.2.4

4bool 어떤 크기의 비트 필드(1 비트 비트 필드 포함)에true 또는 false 값이 저장되면 원래bool값과 비트 필드의 값이 동일하게 비교됩니다. 열거 자의 값이 동일한 열거 유형의 비트 필드에 저장되고 비트 필드의 비트 수가 해당 열거 유형 (10.2)의 모든 값을 보유 할만큼 충분히 큰 경우 원래 열거 자 값과 비트 필드의 값은 동일하게 비교되어야 합니다. [ 예:

enum BOOL { FALSE=0, TRUE=1 };
struct A {
  BOOL b:1;
};
A a;
void f() {
  a.b = TRUE;
  if (a.b == TRUE)    // yields true
    { /* ... */ }
}

— 최종 예]


언뜻 보면 굵은 부분이 해석을 위해 열린 것처럼 보입니다. 그러나, 정확한 의도는 분명해진다 enum BOOL으로부터 유래int .

enum BOOL : int { FALSE=0, TRUE=1 }; // ***this line
struct mystruct { BOOL enabled:1; };
int main()
{
  struct mystruct s;
  s.enabled = TRUE;
  if(s.enabled == TRUE)
    printf("Is enabled\n"); // --> we think this to be printed
  else
    printf("Is disabled !!\n");
}

위의 코드를 사용하면 -Wall -pedantic .

경고 : 'mystruct :: enabled'가 너무 작아서 'enum BOOL'의 모든 값을 보유 할 수 없습니다. struct mystruct { BOOL enabled:1; };

출력은 다음과 같습니다.

비활성화되었습니다 !! (사용할 때enum BOOL : int )

enum BOOL : int간단하게 만든 다면enum BOOL , 출력은 상기 표준 pasage의 지정과 같다 :

활성화 됨 (사용시 enum BOOL)


따라서 다른 답변이 거의없는 것처럼 그 유형은 단일 비트 비트 필드에 값 "1"을 저장할만큼 충분히 크지 않다는 결론을 내릴 수 있습니다 int.


0

내가 볼 수있는 비트 필드에 대한 이해에는 잘못된 것이 없습니다. 내가 보는 것은 먼저 mystruct를 struct mystruct {int enabled : 1; } 그리고 as struct mystruct s; . 코딩해야하는 것은 다음과 같습니다.

#include <stdio.h>

struct mystruct { int enabled:1; };
int main()
{
    mystruct s; <-- Get rid of "struct" type declaration
    s.enabled = 1;
    if(s.enabled == 1)
        printf("Is enabled\n"); // --> we think this to be printed
    else
        printf("Is disabled !!\n");
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.