Opened 4 years ago
#247 new defect
Wrong result type for sizeof(-)
Reported by: | mlbrooks | Owned by: | |
---|---|---|---|
Priority: | minor | Component: | cfa-cc |
Version: | 1.0 | Keywords: | |
Cc: |
Description
The result type of a sizeof(-) expression should be size_t, but it is currently long unsigned int. The difference is observable on 32-bit builds, including on a plain C example.
Our definition of size_t is taken from the host C installation's <stddef.h> and is, therefore, above reproach.
This definition has:
-m32
typedef unsigned int size_t;
-m64
typedef long unsigned int size_t;
C11 Reference Manual, pg 90 has:
"The value of the result of [sizeof] is implementation-defined, and its type (an unsigned integer type) is size_t, defined in <stddef.h> (and other headers)."
Present state has sizeof(-) returning type long unsigned int, always, which is correct for 64-bit and incorrect for 32-bit:
// CFA // expect: collision always // actual: collision 64-bit only void f1( size_t ) {} void f1( typeof( sizeof( 42 ) ) ) {} // expect: collision 32-bit only // actual: collision never void f2( unsigned int ) {} void f2( typeof( sizeof( 42 ) ) ) {} // expect: collision 64-bit only // actual: collision always void f3( long unsigned int ) {} void f3( typeof( sizeof( 42 ) ) ) {}
While it is true that the types unsigned int and long unsigned int are the same size on 32-bit, they are still different types:
// C #include <stddef.h> int main( int argc, char ** argv ) { unsigned int val = 0; long unsigned int lval = 0; unsigned int * p = & lval; long unsigned int * lp = &val; }
GCC, actual and expected, 32- and 64-bit: initialization of ‘unsigned int *’ from incompatible pointer type ‘long unsigned int *’; initialization of ‘long unsigned int *’ from incompatible pointer type ‘unsigned int *’
Typing the sizeof expression in this way means CFA is unable to compile the following valid C program:
// C #include <stddef.h> int main( int argc, char ** argv ) { typeof( sizeof( 42 ) ) val = 0; size_t * p = & val; }
GCC, 64-bit: Success
GCC, 32-bit: Success
CFA, 64-bit: Success
CFA, 32-bit: Fail
Typing the sizeof expression in this way means CFA incurs unintuitive conversion costs in sizeof-vs-size_t overloading scenarios, which leads to unexpected ambiguity:
// CFA void foo( size_t ) { printf("foo, unsigned\n"); } void foo( ptrdiff_t ) { printf("foo, signed\n"); } int main(int argc, char** argv) { printf("size of long int: %ld\n", sizeof(long int)); foo( sizeof(float) ); // sizeof => size_t foo( "hello" - "world" ); // difference of pointers => ptrdiff_t }
CFA, 64-bit, actual and expected: Compiler success with program output:
size of long int: 8 foo, unsigned foo, signed
CFA, 32-bit, actual: Compiler error. Ambiguity at foo(sizeof(float)), having resolved type unsigned long int, between the two declarations, both showing 1 unsafe conversion cost.
CFA, 32-bit, expected: Compiler success with program output:
size of long int: 4 foo, unsigned foo, signed
Initial investigation into the feasibility of changing the sizeof type found:
- SizeofExpr? gets its result type set by its default constructor, and so the "unsigned int" decision made at the conclusion of parsing. It is visible with -XCFA -p -XCFA -P -XCFA ast.
- Validate/FindSpecialDecls?.h populates the size_t type into a global variable.
- To fix, consider deferring the result-type decision from parse time until after Validate::findSpecialDecls, and using the type from the global variable.
- There is no equivalent problem with ptrdiff_t being introduced by subtracting pointers, because that operator is given in the prelude, with the expected ptrdiff_t return type. The difficulty is introduced by the compiler-provided handling of sizeof, taken with the input-provided definition of size_t.