2

I'm currently working on a project, and a particular part needs a multi-line macro function (a regular function won't work here as far as I know).

The goal is to make a stack manipulation macro, that pulls data of an arbitrary type off the stack (being the internal stack from a function call, not a high-level "stack" data type). If it were a function, it'd look like this:

type MY_MACRO_FUNC(void *ptr, type);

Where type is the type of data being pulled from the stack.

I currently have a working implementation of this for my platform (AVR):

#define MY_MACRO_FUNC(ptr, type) (*(type*)ptr); \
    (ptr = /* Pointer arithmetic and other stuff here */)

This allows me to write something like:

int i = MY_MACRO_FUNC(ptr, int);

As you can see in the implementation, this works because the statement which assigns i is the first line in the macro: (*(type*)ptr).

However, what I'd really like is to be able to have a statement before this, to verify that ptr is a valid pointer before anything gets broken. But, this would cause the macro to be expanded with the int i = pointing to that pointer check. Is there any way to get around this issue in standard C? Thanks for any help!

Leonhart231
  • 182
  • 2
  • 10
  • How do you verify if `ptr` is valid? With something which is an expression (and which could be the operand of comma)? And what is the "arithmetic and other stuff"? Can the original value of `ptr` (before the assignment) be recovered? If it can, it's possible, otherwise, you need some temporary object, which would make it at least hard. – mafso Oct 16 '14 at 17:53
  • A ternary operator would likely do what you want, but I easily envision significant problems regardless. Not going into the blatant potential for violating strict aliasing, all you have is a type and a prospect pointer. Supposing it is not, in fact, valid, what *precisely* is you're alternative value source for initializing `i`? (or were you just planning on a flat-out-process-abort)? – WhozCraig Oct 16 '14 at 17:55
  • 2
    @mafso `ptr` would be verified to be non-`NULL` and to have it's expected alignment (aligned to be a valid integer pointer on AVR). Can you explain the "operand of comma" part? The arithmetic just assigns `ptr` to the next spot on the stack after popping off this value. I believe that the original value could be recovered, yes. – Leonhart231 Oct 16 '14 at 18:06
  • 1
    @WhozCraig The alternative would be an `abort` call, yes. – Leonhart231 Oct 16 '14 at 18:07

2 Answers2

5

As John Bollinger points out, macros expanding to multiple statements can have surprising results. A way to make several statements (and declarations!) a single statement is to wrap them into a block (surrounded by dowhile(0), see for example here).

In this case, however, the macro should evaluate to something, so it must be an expression (and not a statement). Everything but declarations and iteration and jump statements (for, while, goto) can be transformed to an expression: Several expressions can be sequenced with the comma operator, if-else-clauses can be replaced by the conditional operator (?:).

Given that the original value of ptr can be recovered (I’ll assume "arithmetic and other stuff here" as adding 4 for the sake of having an example)

#define MY_MACRO_FUNC(ptr, type) \
    ( (ptr) && (uintptr_t)(ptr)%4 == 0 \
        ? (ptr) += 4 , *(type*)((ptr) - 4) \
        : (abort() , (type){ 0 }) )

Note, that I put parentheses around ptr and around the whole expression, see e.g. here for an explanation.

The second and third operand of ?: must be of the same type, so I included (type){0} after the abort call. This expression is never evaluated. You just need some valid dummy object; here, type cannot be a function type.

If you use C89 and can’t use compound literals, you can use (type)0, but that wouldn’t allow for structure or union types.


Just as a note, Gcc has an extension Statements and Declarations in Expressions.

Community
  • 1
  • 1
mafso
  • 5,433
  • 2
  • 19
  • 40
  • Ah, this should do exactly what I need and I learned a lot about the power of the `?:` operator in the process. Thank you very much for your explanation! – Leonhart231 Oct 16 '14 at 19:10
2

This is very nasty:

#define MY_MACRO_FUNC(ptr, type) (*(type*)ptr); \
    (ptr = /* Pointer arithmetic and other stuff here */)

It may have unexpected results in certain inoccuous-looking circumstances, such as

if (foo) bar = MY_MACRO_FUNC(ptr, int);

Consider: what happens then if foo is 0?

I think you would be better off implementing this in a form that assigns the popped value instead of 'returning' it:

#define MY_POP(stack, type, v) do { \
  if (!stack) abort_abort_abort(); \
  v = *((type *) stack); \
  stack = (... compute new value ...); \
} while (0)
John Bollinger
  • 160,171
  • 8
  • 81
  • 157
  • Your example shows why I always use braces after if-statements, haha. But, your point is taken. I would love to implement it like you showed, but I unfortunately cannot alter the macro's signature. However, you've given me the idea of possibly building it *off of* that macro instead. I'll give it a shot, thank you. – Leonhart231 Oct 16 '14 at 18:11