2

Case 1. File: test1.c:

unsigned long val = (unsigned long)&"test";

int main() 
{
    return 0;
}

Compiler invocation: cl test1.c /c

Results:

Microsoft (R) C/C++ Optimizing Compiler Version 19.25.28611 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

test1.c
test1.c(1): warning C4311: 'type cast': pointer truncation from 'char (*)[5]' to 'unsigned long'
test1.c(1): error C2099: initializer is not a constant

Case 2. File: test2.c:

union { unsigned long val; } val =  { (unsigned long)&"test" };

int main() 
{
    return 0;
}

Compiler invocation: cl test2.c /c

Results:

Microsoft (R) C/C++ Optimizing Compiler Version 19.25.28611 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

test2.c
test2.c(1): warning C4311: 'type cast': pointer truncation from 'char (*)[5]' to 'unsigned long'

Question: why after putting unsigned long into union (case 2, file test2.c) the error C2099: initializer is not a constant is gone?

Notes:

  • for both code versions cl x86 (same version as for x64) produces NO errors, NO warnings.

  • for both code versions gcc x86/x64 (version 9.3.0) produces NO errors, NO warnings.

UPD. Please note: the question is not about safe code / unsafe code or wrong code / right code. The question is about cl compiler behavior. I.e. why in the 2nd case the cl considers that initializer IS a constant (such conclusion is from the absence of the error message).

pmor
  • 5,392
  • 4
  • 17
  • 36
  • 1
    What you're doing is wrong. Most importantly because the Microsoft Visual Studio compiler keeps `long` (and therefore `unsigned long`) as 32-bit types. On a 64-bit system a pointer is 64 bits wide, and it can't fit in a 32-bit variable. – Some programmer dude Jun 02 '20 at 20:33
  • What makes you think it's safe to try to cram a pointer value into an `unsigned long`? – Andrew Henle Jun 02 '20 at 20:34
  • @Someprogrammerdude: Conversions from pointers to integer type are explicitly permitted by the C standard, even if the destination is too narrow to represent the full address information. The result is implementation-defined and can be useful for, for example, alignment issues. In any case, whether the code is “wrong” or not is not the question. The difference in compiler behavior is. – Eric Postpischil Jun 02 '20 at 20:40
  • @AndrewHenle: OP has given us a well-posed question with MRE, identification of compiler version, and exact compiler messages. The question demonstrates cognizance of language issues and does raise a non-trivial issue about compiler behavior. OP may have reduced other code to this MRE, and the above qualities of the question suggest they are also aware of the meaning of the conversion warnings. They deserve the benefit of the doubt that they know what they are doing and do not deserve knee-jerk nitpicking about issues in code that may be due to being an MRE rather than full real code. – Eric Postpischil Jun 02 '20 at 20:53

1 Answers1

1

Initializers for objects of static storage duration must be constant expressions .

In the C89 standard (the only standard to which Microsoft C conforms) , a cast from pointer to integer is not permitted in a constant expression.

This is a Semantic rule and not a Constraint, which means the program has undefined behaviour with no diagnostic required. Therefore the compiler is permitted to reject the program (but is not required to reject it), it may or may not issue diagnostics, and there is no requirement of consistency amongst various programs that are undefined.

We cannot conclude from the absence of an Error message that the initializer was accepted as a constant .

Since C99, the standard also includes the text "An implementation may accept other forms of constant expressions". The compiler should publish conformance documentation listing which expressions it accepts as constants, although I couldn't find that documentation for MSVC. (leave a comment if you can!)

It may also be relevant to note the rules about casting pointer to unsigned long. From the latest Standard:

Any pointer type may be converted to an integer type. Except as previously specified, the result is implementation-defined. If the result cannot be represented in the integer type, the behavior is undefined. The result need not be in the range of values of any integer type.

So, even if the C99 or C11-compliant compiler documents that it accepts casts from pointer to integer in constant expressions, it may still be that the result of the cast causes a trap on startup, or causes undefined behaviour (which , as before, means no diagnostic is required and the program may be rejected).

M.M
  • 138,810
  • 21
  • 208
  • 365
  • Thanks for the answer! Have to think about. A quick question: any idea, why in both cases `cl x86` generates NO errors and NO warnings? Cause now the behavior looks like `cl x86` and `cl x64` are two different implementations (are they?) and use different logic. See also (might be interesting): https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66618. – pmor Jun 02 '20 at 23:37
  • 1
    @pmor I doubt if anyone could answer that other than a developer of `cl`, maybe one of them will see this question . We can only speculate on how a closed source thing works internally. The difference might be to do with the point in my final paragraph that converting 64-bit pointer to 32-bit int definitely has some lossy cases while 32-bit pointer to 32-bit int is likely to be implementation-defined to something sensible in all cases. Or perhaps it might be to do with C++ logic leaking into the C compiler (in C++ the cast is ill-formed requiring diagnostic if and only if it might be lossy) – M.M Jun 02 '20 at 23:47
  • Thanks! Agree with `64-bit pointer to 32-bit int definitely has some lossy cases while 32-bit pointer to 32-bit int is likely to be implementation-defined to something sensible in all cases`. About possible `C++ logic leaking into the C compiler`: interesting idea. Have a look at this related invalid bug: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=60165. Citation: `there is no guarantee you get the same warnings between different optimization levels`. So, in case of `cl` there is no guarantee you get the same errors between different bit depths (i.e. 32/64-bit) of target processor. – pmor Jun 17 '20 at 22:06