Why is this addition being silently ignored? - c

I have the following C code:
#include <stdint.h>
#include <stdio.h>
int i;
uint64_t a[] = { (uint64_t)&i, (uint64_t)&i + 0x8000000000000000 };
int main() {
printf("%p %llx %llx\n", &i, a[0], a[1]);
}
If I compile this (as C or as C++) with Microsoft Visual Studio Community 2015 and then run it, the output is similar to the following:
013E9154 13e9154 13e9154
It seems that the code + 0x8000000000000000, which I expected to set the high bit of a[1], has been silently ignored.
However, if I move the initialization of a inside main, the output is what I would expect:
00179154 179154 8000000000179154
With a global, why is the addition being silently ignored? Should the attempted addition actually set the high bit of a[1] or should it cause a compiler error?
Interestingly, if + 0x8000000000000000 in the above code is replaced by | 0x8000000000000000, I get "error C2099: initializer is not a constant".
Edit: A similar issue can occur even in the absence of casts. Compiled for x64, the following code prints the same value (e.g. 000000013FB8D180) three times:
#include <stdio.h>
int i;
int * a[] = { &i, &i + 0x100000000 };
int main() {
printf("%p %p %p\n", &i, a[0], a[1]);
}

The initializer
(uint64_t)&i + 0x8000000000000000
isn't a valid constant expression in C. It is neither an arithmetic constant expression which only allows integer constants, floating constants, enumeration constants, character constants, and sizeof expressions as operands; nor an address constant which doesn't allow casts to integer types.
That said, I'd expect Visual Studio to generate "error C2099: initializer is not a constant" like it does with | 0x8000000000000000.
I'm not sure about C++, though.

None of the initializers used in
uint64_t a[] = { (uint64_t)&i, (uint64_t)&i + 0x8000000000000000 };
are eligible constant expressions. The pedantic definition of constant expression in C does not allow casting pointer values to integer types, even if the pointer values satisfies requirements for address constant. Which means that formally (uint64_t)&i is already illegal in this context.
However, this compiler apparently accepts (uint64_t)&i in this context as an extension.
After that the fact that it complains when + is replaced with | operator is probably rooted directly in the language specification
6.6 Constant expressions
7 More latitude is permitted for constant expressions in initializers.
Such a constant expression shall be, or evaluate to, one of the
following:
— an arithmetic constant expression,
— a null pointer constant,
— an address constant, or
— an address constant for an object type plus or minus an integer constant expression.
Again, this is not an exact match, since the above wording allows adding fixed offset to address constants only, but for a compiler that accepts (uint64_t)&i as a constant expression in this context it wouldn't be unusual to continue to apply the "plus or minus" restriction. The ability to add something to (or subtract something from) an address constant in C is defined by the capabilities of loaders that perform address relocation at load time. Loaders can add or subtract, but they cannot perform bitwise operations on addresses.
And, finally, the fact that it has no effect at run time is apparently caused by the limitations of the loader, which is responsible for implementing C-style initialization of statics at startup time.

Related

clang/gcc cannot set global variables to an address constant minus another address constant

The program below compiles without errors.
#include <stdio.h>
char addr_a[8];
char addr_b[8];
unsigned long my_addr = (unsigned long)addr_b - 8; // PASS
// unsigned long my_addr = (unsigned long)addr_b - (unsigned long)addr_a; // FAIL (error: initializer element is not constant)
int main() {
printf("%lx\n", my_addr);
return 0;
}
Interestingly, when I set unsigned long my_addr = (unsigned long)addr_b - (unsigned long)addr_a the compiler throws "error: initializer element is not constant."
I know globals can only be initialized with a constant expression. I also know that the types of constant expressions that can be used in an initializer for a global are specified in section 6.6p7 of the C standard:
More latitude is permitted for constant expressions in initializers. Such a constant expression shall be, or evaluate to, one of the following:
an arithmetic constant expression,
a null pointer constant,
an address constant, or
an address constant for a complete object type plus or minus an integer constant expression.
Note that an address constant minus an integer constant is allowed, but not an address constant minus another address constant.
Question:
Why does the C standard restrict the ways you can initialize global variables? What is stopping the C standard from accepting unsigned long my_addr = (unsigned long)addr_b - (unsigned long)addr_a?
Why would you want this?
Suppose addr_a and addr_b represent the start and end of the .text section respectively. A program may want to map the .text section, which has size (unsigned long)addr_b - (unsigned long)addr_a. The trusted-firmware-a project does this in Boot Loader stage 2 (BL2). See BL_CODE_END - BL_CODE_BASE, which is used in arm_bl2_setup.c.
Objects with static storage duration (i.e. globals, plus locals defined as static) can only be initialized with a constant expression.
The types of constant expression that can be used in an initializer for such an object is specified in section 6.6p7 of the C standard:
More latitude is permitted for constant expressions in
initializers. Such a constant expression shall be, or evaluate to,
one of the following:
an arithmetic constant expression,
a null pointer constant,
an address constant, or
an address constant for a complete object type plus or minus an integer constant expression.
Note that an address constant plus an integer constant is allowed, but not an address constant plus another address constant.
Granted this still isn't exactly what you have, as you have address constants casted to integer type. So let's check 6.6p6 which defines an integer constant expression:
An integer constant expression shall have integer type and
shall only have operands that are integer constants, enumeration
constants, character constants, sizeof expressions whose results
are integer constants, _Alignof expressions, and floating
constants that are the immediate operands of casts. Cast operators in
an integer constant expression shall only convert arithmetic
types to integer types, except as part of an operand to the
sizeof or _Alignof operator.
This paragraph doesn't allow for casting an address constant to an integer type as part of an integer constant expression, but apparently this seems to be supported as an extension.
What is stopping the C standard from accepting unsigned long my_addr = (unsigned long)addr_a + (unsigned long)addr_b?
The underlying reason is "Because why would anyone want that?" It's not meaningful to add two absolute addresses together; the result isn't the address of anything in particular.
It's thus a sort of chicken-and-egg thing. The language doesn't support it because it's useless, but also because existing linkers and object file formats don't support such a relocation. For instance, for ELF on x86-64, see the psABI Table 4.9 for a list of supported relocations, and note there is no S+S. And the linkers don't support it because it's useless, and because the language doesn't require it to be supported.
I guess originally, the tools probably came before the language (the earliest C compilers would have used linkers designed for assembly programs). So the original tools probably didn't support this, the language saw no need to demand that they do so, and over time, neither one ever saw a need to add it.

What does this statement about pointer operators mean? "& can be used only with a variable, * can be used with variable, constant or expression."

'Address of' operator gives memory location of variables. So it can be used with variables.
I tried compiling this code.
#include<stdio.h>
int main()
{
int i=889,*j,*k;
j=&889;
k=*6422296;
printf("%d\n",j);
return 0;
}
It showed this error error: lvalue required as unary '&' operand for j=&889.
And I was expecting this error: invalid type argument of unary '*' (have 'int')| for k=*6422296.
6422296 is the memory location of variable i.
Can someone give examples of when '*' is used with constants and expressions?
P.S:- I have not yet seen any need for this But....
All constants in a program are also assigned some memory. Is it possible to determine their address with &? (Just wondering).
An expression that is a value (rvalue in C idom) may not represent a variable with a defined lifetime and for that reason you cannot take its address.
In the opposite direction, it is legal (and common) to dereference an expression:
int a[] = {1,2,3}
int *pt = a + 1; // pt points to the second element of the array
inf first = *(a - 1); // perfectly legal C
Dereferencing a constant is not common in C code. It only makes sense when dealing directly with the hardware, that is in kernel mode, or when programming for some embedded systems. Then you can have special registers that are mapped at well known addresses.
first_byte_of_screen = *((char *) 0xC0000); // may remember things to old MS/DOS programmers
But best practices would recommed to define a constant
#define SCREEN ((unsigned char *) 0xC0000)
first_byte = *SCREEN; // or even SCREEN[0] because it is the same thing
k=*6422296 means go to address number 6422296, read the content inside it and assign it to k, which is completely valid.
j=&889 means get me the address of 889, 889 is an rvalue, it's a temporary that theoretically only exists temporarily in the CPU registers, and might never even get stored in the memory, so asking for it's memory address makes no sense.
All constants in a program are also assigned some memory.
That's not necessarily the case for numeric literals; they're often hardcoded into the machine code instructions with no storage allocated for them.
A good rule of thumb is that anything that can't be the target of an assignment (such as a numeric literal) cannot be the operand of the unary & operator1.
6.5.3.2 Address and indirection operators
Constraints
1 The operand of the unary & operator shall be either a function designator, the result of a
[] or unary * operator, or an lvalue that designates an object that is not a bit-field and is
not declared with the register storage-class specifier.
2 The operand of the unary * operator shall have pointer type.
C 2011 Online Draft
*6422296 "works" (as in, doesn't result in a diagnostic from the compiler) because integer expressions can be converted to pointers:
6.3.2.3 Pointers
...
5 An integer may be converted to any pointer type. Except as previously specified, the
result is implementation-defined, might not be correctly aligned, might not point to an
entity of the referenced type, and might be a trap representation.67)
67) The mapping functions for converting a pointer to an integer or an integer to a pointer are intended to
be consistent with the addressing structure of the execution environment.
Like all rules of thumb, there are exceptions; array expressions are lvalues, but cannot be the target of an assignment; if you declare an array like int a[10];, then you can't reassign a such as a = some_other_array_expression ;. Such lvalues are known as non-modifiable lvalues.

Not a constant initializer element?

I encountered a confusing case when I was doing semantic analysis for my compiler course.
#include <stdio.h>
int a = "abcd"[2];
int main()
{
char b = "abcd"[2];
printf("%d\n%c\n", a, b);
return 0;
}
GCC says "error: initializer element is not constant" for variable "a".
Why?
The C language requires initializers for global variables to be constant expressions. The motivation behind this is for the compiler to be able to compute the expression at compile time and write the computed value into the generated object file.
The C standard provides specific rules for what is a constant expression:
An
integer constant expression117)
shall have integer type and shall only have operands
that are integer constants, enumeration constants, character constants,
sizeof
expressions whose results are integer constants,
_Alignof
expressions, and floating
constants that are the immediate operands of casts. Cast operators in an integer constant
expression shall only convert arithmetic types to integer types, except as part of an
operand to the
sizeof
or
_Alignof
operator
.
More latitude is permitted for constant expressions in initializers. Such a constant
expression shall be, or evaluate to, one of the following:
an arithmetic constant expression,
a null pointer constant,
an address constant, or
an address constant for a complete object type plus or minus an integer constant
expression.
As you can see non of the cases include an array access expression or a pointer dereference. So "abcd"[2] does not qualify as a constant expression per the standard.
Now the standard also says:
An implementation may accept other forms of constant expressions.
So it would not violate the standard to allow "abcd"[1] as a constant expression, but it's also not guaranteed to be allowed.
So it's up to you whether or not to allow it in your compiler. It will be standard compliant either way (though allowing it is more work as you need another case in your isConstantExpression check and you need to actually be able to evaluate the expression at compile time, so I'd go with disallowing it).
int a = "abcd"[2];
a is a global variable initilize at compile time but the "abcd"[2] is computed at run time.
char b = "abcd"[2];
here b is local variable and it initilize at run time after "abcd"[2] computed.

Initializer with constant expression having possible overflow in C99

Is this valid C99 code? If so, does it define an implementation-defined behavior?
int a;
unsigned long b[] = {(unsigned long)&a+1};
From my understanding of the C99 standard, from §6.6 in the ISO C99 standard, this might be valid:
An integer constant expression shall have integer type and shall only have operands that are integer constants (...) Cast operators in an integer constant expression shall only convert arithmetic types to integer types, except as part of an operand to the sizeof operator.
More latitude is permitted for constant expressions in initializers. Such a constant expression shall be, or evaluate to, one of the following:
an arithmetic constant expression,
(...)
an address constant for an object type plus or minus an integer constant expression.
However, because there is the possibility of the addition overflowing, this might not be considered a constant expression and therefore not valid C99 code.
Could someone please confirm if my reasoning is correct?
Note that both GCC and Clang accept this code without warnings, even when using -std=c99 -pedantic. However, when casting to unsigned int instead of unsigned long, that is, using the following code:
int a;
unsigned long b[] = {(unsigned int)&a+1};
Then both compilers complain that the expression is not a compile-time constant.
From this clang developers thread on a similar issue: Function pointer is compile-time constant when cast to long but not int? the rationale is that the standard does not require the compiler to support this(this scenario is not included in any of bullets in 6.6p7) and although it is allowed to support this supporting truncated addresses would be burdensome:
I assume that sizeof(int) < sizeof(void(*)()) == sizeof(long) on
your target. The problem is that the tool chain almost certainly
can't express a truncated address as a relocation.
C only requires the implementation to support initializer values that
are either (1) constant binary data, (2) the address of some object, or
(3) or an offset added to the address of some object. We're allowed,
but not required, to support more esoteric things like subtracting two
addresses or multiplying an address by a constant or, as in your
case, truncating the top bits of an address away. That kind of
calculation would require support from the entire tool chain from
assembler to loader, including various file formats along the way.
That support generally doesn't exist.
Your case, which is casting a pointer to a integer type does not fit any of the cases under 6.6 paragraph 7:
More latitude is permitted for constant expressions in initializers.
Such a constant expression shall be, or evaluate to, one of the
following:
an arithmetic constant expression,
anull pointer constant,
an address constant, or
an address constant for an object type plus or minus an integer constant expression.
but as mentioned in the post compiler are allowed to support other forms of constant expression:
An implementation may accept other forms of constant expressions.
but neither clang nor gcc accept this.
This code is not required to be accepted by a conforming implementation. You quoted the relevant passage in your question:
More latitude is permitted for constant expressions in initializers. Such a constant expression shall be, or evaluate to, one of the following:
an arithmetic constant expression,
a null pointer constant,
an address constant, or
an address constant for an object type plus or minus an integer constant expression.
(unsigned long)&x is none of those things. It's not an arithmetic constant because of C11 6.6/8:
Cast operators in an arithmetic constant expression shall only convert
arithmetic types to arithmetic types
(pointer types are not arithmetic types, 6.2.5/18); and it is not an address constant because all address constants are pointers (6.6/9). Finally a pointer plus or minus an ICE is another pointer, so it is not that either.
However 6.6/10 says that an implementation may accept other forms of constant expressions. I'm not sure whether this means the original code should be called ill-formed or not (ill-formed code requires a diagnostic). Clearly your compiler is accepting some other constant expressions here.
The next issue is that casting from pointer to integer is implementation-defined. It may also be undefined if there is no integer representation corresponding to the particular pointer. (6.3.2.3/6)
Finally, the + 1 on the end makes no difference. unsigned long arithmetic is well-defined on addition and subtraction, so it is OK if and only if (unsigned long)&x is OK.
First of all, your initializer is not necessarily a constant expression. If a has local scope, then it is assigned an address in run-time, when it gets pushed on the stack. C11 6.6/7 says that in order for a pointer to be a constant expression, it has to be an address constant, which is defined in 6.6/9 as:
An address constant is a null pointer, a pointer to an lvalue
designating an object of static storage duration, or a pointer to a
function designator; it shall be created explicitly using the unary &
operator or an integer constant cast to pointer type, or implicitly by
the use of an expression of array or function type.
(Emphasis mine)
As for whether your code is standard C, yes it is. Pointer conversions to integers are allowed, although they may come with various forms of poorly specified behavior. Specified in 6.5/6:
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.
To safely ensure that the pointer can fit into the integer, you need to use uintptr_t. But I don't think pointer to integer conversion was the reason you posted this question.
Regarding whether an integer overflow would prevent it from being a compile time constant, I'm not sure where you got that idea from. I don't believe your reasoning is correct, for example (INT_MAX + INT_MAX) is a compile time constant and it is also guaranteed to overflow. (GCC gives you a warning.) In case it overflows, it will invoke undefined behavior.
As for why you get errors about the expression not being a compile-time constant, I don't know. I can't reproduce it on gcc 4.9.1. I tried declaring a with both static and automatic storage duration but no difference.
Sounds like you somehow accidentally compiled as C90, in which case gcc will tell you "error: initializer element is not computable at load time". Or maybe there was a compiler bug which has been fixed in my version of gcc.

Address vs. expression on address in static initializer in C

(This question was inspired by investigating an earlier question)
I have a code sample that initializes two global static variables: one is a pointer to extern variable, another is an expressioon computed from that pointer:
#include <stdint.h>
#define UNCACHE_MASK 0xABCDEF12UL // Value of the mask to apply
extern int memory_area;
const void * virtual_address = &memory_area;
const uintptr_t int_address = ((uintptr_t)&memory_area) | UNCACHE_MASK;
When I compile I get the following:
$ gcc -c test.c
test.c:6:1: error: initializer element is not computable at load time
const uintptr_t int_address = ((uintptr_t)&memory_area) | UNCACHE_MASK;
^
I am not much of an expert in C, but it seems that if &memory area is good for initializing virtual_address, it should also be good for initializing int_address.
What am I missing?
(gcc version 4.8.2, Cygwin on Win 7)
The formal definition of constant expressions of C language allow converting integer values to pointer types (to form address constants), but not the other way around (to form arithmetic constant expressions). It explicitly states that "cast operators in an arithmetic constant expression shall only convert arithmetic types to arithmetic types". For this reason the (uintptr_t) &memory_area bit violates the requirements imposed on arithmetic constant expressions. The expression is formally not a constant expression and therefore cannot be used in initializer for an object with static storage duration.
So, in short, &memory_area is an address constant, but (uintptr_t) &memory_area is not an arithmetic constant expression.
But it is indeed strange to see that GCC does not allow it as an extension.

Resources