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 assigned int
or the same type asunsigned 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 asint
, then it is implementation-defined whether the bit-field is signed or unsigned. This includes anint
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.
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.
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;
};
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?
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.
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?
};