3

This is some startup file excerpt with interrupt vectors.

#pragma DATA_SECTION(interruptVectors, ".intvects")
void (* const interruptVectors[])(void) =
{
  (void (*) (void))((uint32_t)&__STACK_END),
  resetISR,
  nmi_ISR,
  fault_ISR,
  ... /* More interrupt vectors */

void (* const interruptVectors[])(void) - is an array of function pointers that must contain function names, but I can't understand the (void (*) (void))((uint32_t)&__STACK_END) syntax.

(void (*) (void)) looks like a pointer to a function that returns nothing, without arguments and doesn't have any name. Is it possible?

(uint32_t)&__STACK_END is a pointer. And why are the function pointer and pointer together?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
kancler
  • 81
  • 1
  • 8
  • 7
    It's a _cast_ to a pointer to a function that returns nothing and has no arguments. The thing being cast is the address of `__STACK_END`, which was itself first cast to `uint32_t`. – Useless Jun 28 '22 at 12:22
  • 3
    `((uint32_t)&__STACK_END)` says convert the address of `_STACK_END` into a `uint32_t`. `(void (*) (void))` says convert that value into a pointer to a function that takes no parameters and returns nothing. – NathanOliver Jun 28 '22 at 12:22
  • 2
    Without additional context this looks like a crude attempt to put a magical constant into an array of function pointers. __STACK_END is a magical constant. – Sam Varshavchik Jun 28 '22 at 12:25
  • 2
    This is a good an example the one `typedef` can greatly improve code readability. I would define `typedef void (*InterruptFuncPtr)(void);` then this array is nce and causy: `const InterruptFuncPtr interruptVectors[] = { (InterruptFuncPtr)(uint32_t)&__STACK_END, ....`. – Marek R Jun 28 '22 at 12:49
  • @Useless thank you! I read it several times and understood what you meant :) – kancler Jun 28 '22 at 12:49
  • 3
    Note the cast to `uint32_t` will truncate the address on a 64bit build. Better to use `uintptr_t` instead. – Remy Lebeau Jun 28 '22 at 16:58
  • @MarekR RemyLebeau Thanks! I'll take it on board :) – kancler Jun 29 '22 at 07:38

2 Answers2

6

This looks like the interrupt vector table for an ARM processor or similar. The interrupt vector table contains the addresses of interrupt handlers, so it is essentially an array of function pointers.

The first entry of this table is the initialization value for the stack pointer. It's obviously not a function pointer, but a data pointer, so some type conversion is needed. Not because the processor cares about types, but because C does.

So &__STACK_END is presumable some pointer type which points to a data address at the end of the stack. This is then converted to a plain 32-bit number, and finally converted to a function pointer.

It might have been possible to skip the first cast to uint32_t and cast directly from a data pointer to a function pointer, if the compiler supported it as an extension. But strictly speaking, in the C standard conversion from a data pointer directly to a function pointer is not legal, and cast to interger is necessary.

There are also additional implemetation defined issues programmer must consider for this kind conversion to work: sizes of types and alignments must be compatible, there must not be trap representations, etc. This is all normal when working with code that is close to hardware.

user694733
  • 15,208
  • 2
  • 42
  • 68
  • @MarekR Conversion between data and function pointer is not well defined, but conversion between pointer and integer is. So developer just wanted to make sure that conversion is done correctly, or perhaps he just wanted to silence a compiler warning. In ARM all 3 types are 32-bit and there is not really change in actual address value. It is all just to keep compiler happy. – user694733 Jun 28 '22 at 12:59
  • https://stackoverflow.com/questions/1096341/ – Remy Lebeau Jun 28 '22 at 16:59
  • `(uint32_t)` reduces future compatibility when the pointers are wider. `(uintptr_t)` makes more sense. – chux - Reinstate Monica Aug 27 '22 at 23:19
  • "conversion from a data pointer to a function pointer is not legal, even with an integer cast in the middle." --> C has "Any pointer type may be converted to an integer type" and "An integer may be converted to any pointer type." so it is legal, with the usual caveats of size, alignment, implementation defined behavior, ... – chux - Reinstate Monica Aug 27 '22 at 23:24
  • @chux-ReinstateMonica Thank you; I revised the part about integer conversion. I remembered that would have been more strict than what it was. – user694733 Aug 29 '22 at 07:54
  • @chux-ReinstateMonica I wouldn't be too worried about `uint32_t` vs `uintptr_t` in this case. This is very hardware dependent code, and it is extremely unlikely it would work without complete rewrite if pointer width changes. – user694733 Aug 29 '22 at 08:02
1

The first value of the vector table of a Cortex-M is the initial value of the stack pointer, and that looks like your case. This syntax is a hack to define the whole vector table as a constant array of function pointer of type void(*function)(void) while defining the first value as the stack pointer value as a constant.

Personally I think there are better ways to define this more clearly.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Damiano
  • 698
  • 7
  • 19