6

In C++, it is possible for pointer values to be compile-time constants. This is true, otherwise, non-type template parameters and constexpr won't work with pointers. However, as far as I know, addresses of functions and objects of static storage are known (at least) at link-time rather than compile-time. Following is an illustration:

main.cpp

#include <iostream>

template <int* p>
void f() { std::cout << p << '\n'; }

extern int a;

int main() {
    f<&a>();
}

a.cpp

int a = 0;

I'm just wondering how the address of a could possibly be known when compiling main.cpp. I hope somebody could explain this a little to me.

In particular, consider this

template <int* p, int* pp>
constexpr std::size_t f() { 
  return (p + 1) == (pp + 7) ? 5 : 10; 
}

int main() {
    int arr[f<&a, &b>()] = {};
}

How should the storage for arr be allocated?

PLUS: This mechanism seems to be rather robust. Even when I enabled Randomized Base Address, the correct output is obtained.

Lingxi
  • 14,579
  • 2
  • 37
  • 93
  • 3
    In g++, the name of the variable whose address is taken is mangled into the function name: http://coliru.stacked-crooked.com/a/ee352366c870c010 – dyp Apr 25 '15 at 15:25
  • @dyp The linker will ultimately adjust the address value `p` in binary code of the template instantiation. May I understand it this way? – Lingxi Apr 25 '15 at 15:31
  • It is just an observation, not an answer. I do not know how exactly this is implemented. But also consider variables and functions with external linkage. For those, the addresses might also not be known at compile time, if their definition resides in another TU. In g++'s disassembly, you'll find operations such as `movl $42, name_of_the_external_variable(%rip)` which should be independent of the template nature of the function. – dyp Apr 25 '15 at 15:34

2 Answers2

4

The compiler doesn't need to know the value of &a at compile time any more than it needs the value of function addresses.

Think of it like this: the compiler will instantiate your function template with &a as a parameter and generate "object code" (in whatever format it uses to pass to the linker). The object code will look like (well it won't, but you get the idea):

func f__<funky_mangled_name_to_say_this_is_f_for_&a>__:
   reg0 <- /* linker, pls put &std::cout here */
   reg1 <- /* hey linker, stuff &a in there ok? */
   call std::basic_stream::operator<<(int*) /* linker, fun addr please? */
   [...]

If you instantiate f<b&>, assuming b is another global static, compiler does the same thing:

func f__<funky_mangled_name_to_say_this_is_f_for_&b>__:
   reg0 <- /* linker, pls put &std::cout here */
   reg1 <- /* hey linker, stuff &b in there ok? */
   call std::basic_stream::operator<<(int*) /* linker, fun addr please? */
   [...]

And when your code calls for calling either of those:

fun foo:
   call f__<funky_mangled_name_to_say_this_is_f_for_&a>__ 
   call f__<funky_mangled_name_to_say_this_is_f_for_&b>__

Which exact function to call is encoded in the mangled function name. The generated code doesn't depend on the runtime value of &a or &b. The compiler knows there will be such things at runtime (you told it so), that's all it needs. It'll let the linker fill in the blanks (or yell at you if you failed to deliver on your promise).


For your addition I'm afraid I'm not familiar enough about the constexpr rules, but the two compilers I have tell me that this function will be evaluated at runtime, which, according to them, makes the code non-conforming. (If they're wrong, then the answer above is, at least, incomplete.)

template <int* p, int* pp>
constexpr std::size_t f() { 
  return (p + 1) == (pp + 7) ? 5 : 10; 
}

int main() {
    int arr[f<&a, &b>()] = {};
}

clang 3.5 in C++14 standards conforming mode:

$ clang++ -std=c++14 -stdlib=libc++ t.cpp -pedantic
t.cpp:10:10: warning: variable length arrays are a C99 feature [-Wvla-extension]
  int arr[f<&a, &b>()];
         ^
1 warning generated.

GCC g++ 5.1, same mode:

$ g++ -std=c++14 t.cpp -O3 -pedantic
t.cpp: In function 'int main()':
t.cpp:10:22: warning: ISO C++ forbids variable length array 'arr' [-Wvla]
   int arr[f<&a, &b>()];
Mat
  • 202,337
  • 40
  • 393
  • 406
  • Then what about this `template constexpr std::size_t f() { return (p + 1) == (pp + 7) ? 5 : 10; }`, and I use the returned value as the bound in an array definition? – Lingxi Apr 25 '15 at 15:52
  • Then how to allocate the storage for the array `int arr[f<&a, &b>()] = {};`? – Lingxi Apr 25 '15 at 15:55
  • Interesting question. Can't get it to compile in C++11 mode though (clang++ and g++ tell me that's a VLA). Same with C++14 mode (g++ 5.1/clang++ 3.5) – Mat Apr 25 '15 at 16:01
  • ... which seems to indicate that both compilers would do that `constexpr` function evaluation at runtime. I'm not familiar enough with the constexpr rules to say whether that's normal or not, sorry. – Mat Apr 25 '15 at 16:08
  • Thanks for your answer anyway. It is helpful. The increasing complexity of C++ is just getting me crazy ~>_<~ – Lingxi Apr 25 '15 at 16:23
  • Yes, some parts are a bit scary. I'd suggest you don't accept this answer for a while (or at all; it's not an obligation and you're of course free to accept any other). That makes it more likely that a real C++ expert will look into it and hopefully add an answer or comment about your addition. – Mat Apr 25 '15 at 16:24
  • 2
    @Lingxi and Mat `pp+7` is Undefined Behaviour if `pp` points to a single integer. Undefined Behaviour may not occur inside a constant expression (use `constexpr auto x = f<&a, &b>();` for a better diagnostic from clang). On the other hand, you may not pass an element of an array to a non-type template parameter of pointer type due to the current restrictions on non-type template arguments (these restrictions probably are made to prevent exactly these more complicated cases). – dyp Apr 25 '15 at 17:56
1

As far as I know, the variables of static storage and functions are stored simply as symbols/place holders in the symbol table while compiling. It is in the linking phase when the place holders are resolved.

The compiler outputs machine code keeping the placeholders intact. Then the linker replaces the placeholders of the variables / functions with their respective memory locations. So in this case too, if you just compile main.cpp without compiling a.cpp and linking with it, you are bound to face linker error, as you can see here http://codepad.org/QTdJCgle (I compiled main.cpp only)

Rajan Prasad
  • 1,582
  • 1
  • 16
  • 33