Issue 1007: Implicitly unsigned bit-fields ambiguity

Authors: Joseph Myers
Date: 2025-03-11
Submitted against: C23
Status: Open
Cross-references: 0315

There are several cases in which the rule that bit-fields declared with types that are not explicitly signed or unsigned might be treated as if either signed or unsigned had been specified leaves ambiguity about exactly what is permitted. One such case dates back to C90, and others have arisen over time as features have been added to the standard (but they are all included together in this issue report given how closely related they are).

In C23, the rule is specified in 6.7.3.1 (Type specifiers):

Each of the comma-separated multisets designates the same type, except that for bit-fields, it is implementation-defined whether the specifier int designates the same type as signed int or the same type as unsigned int.

Footnote 132, in 6.7.3.2 (Structure and union specifiers), explicitly notes that such a type might be produced from a typedef name or typeof specifiers:

As specified in 6.7.3, if the actual type specifier used is int or a typedef-name defined as int, then it is implementation-defined whether the bit-field is signed or unsigned. This includes an int type specifier produced using the typeof specifiers (6.7.3.6).

(It's not entirely clear that this inclusion of typedef names is adequately supported by the normative text, but that's not the subject of this issue report.)

The final response to issue 0315 said the implementation-defined signedness also applied for other implementation-defined declared types such as char, short, long and long long.

No specific wording is suggested to address the questions here, as direction from the committee is needed. For C2Y, my preference would be to follow C++14 and remove the option for bit-fields to be implicitly unsigned when the declared type is not unsigned, but that would need a separate paper and not be appropriate to apply to C23 (notwithstanding that C++ core issue 739 was considered a defect for C++). If that change were made for C2Y, it might then be appropriate to declare most of these cases explicitly unspecified for C23; if no such change is made for C2Y, further consideration should be given to specifying some of these cases explicitly.

Question 1

Suppose the declared type of the bit-field is a typedef name defined in a standard header and required to be a signed integer type. Must that type also be signed as a bit-field, or might it be unsigned (for example, plain int)?

#include <stddef.h>
#include <stdint.h>

struct s1 {
  ptrdiff_t b1a : 20;
  int32_t b1b : 20;
};

Although ptrdiff_t illustrates that this issue was present in C90, it may be more likely to be encountered with types such as int32_t.

If the option for bit-fields to be implicitly unsigned is not removed, it might be appropriate to require typedefs in standard headers that are required to be signed integer types to be signed as bit-field types as well.

Question 2

C11 introduced the rule that typedef names can be redefined with the same type in the same scope. But what if the type is only the same outside of bit-fields? Is the type of the bit-field considered explicitly signed when one definition uses signed and another does not?

typedef int T;
typedef signed int T;

struct s2 {
  T b2 : 20;
};

Question 3

In some cases, it seems clear whether a type from the typeof specifiers should be considered explicitly signed. For example, typeof specifiers applied to a type name do not introduce any issues, and if the typeof specifiers are applied to an expression referring to a single declaration, or to a cast, it seems clear whether the resulting type is explicitly signed.

int i;
signed int si;

struct s3a {
  typeof (int) b3a : 20; // not explicitly signed
  typeof (signed int) b3b : 20; // explicitly signed
  typeof (i) b3c : 20; // not explicitly signed
  typeof (si) b3d : 20; // explicitly signed
  typeof ((int) si) b3e : 20; // not explicitly signed
  typeof ((signed int) i) b3f : 20; // explicitly signed
};

In some other cases, it is less clear: where composite types are involved, or usual arithmetic conversions, for example.

int i;
signed int si;
extern int x;
extern signed int x;

struct s3b {
  typeof (x) b3g : 20;
  typeof (i + si) b3h : 20;
};

In some further cases, the type appears to be specified as not explicitly signed (and so potentially unsigned as a bit-field) because the standard says int rather than signed int, but may not have been considering this issue.

signed short s;

struct s3c {
  typeof (+s) b3i : 20; // integer promotions convert signed short to int
  typeof (s == s) b3j : 20; // comparisons return int
  typeof (0) b3k : 20; // this integer literal has type int
  typeof (-1) b3l : 20; // negating an int literal leaves type int
};

Which of the above cases should be considered explicitly signed, and which should not?

Question 4

Does the rule about implementation-defined signedness include _BitInt?

struct s4 {
  _BitInt (32) b4 : 20; // may this be unsigned?
};

I think there is a reasonable case to answer "no" to this question based only on the current wording, on the basis that the response to issue 0315 was only concerned with implementation-defined bit-field types, and support for _BitInt types for bit-fields is not implementation-defined.

Question 5

If the answer to Question 4 is "yes", does the constraint disallowing a type _BitInt(1) apply before or after the reinterpretation as unsigned for a bit-field?

struct s5 {
  _BitInt (1) b5 : 1; // is this valid if _BitInt bit-fields are unsigned?
};