Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is this mysterious macro plus sign in stdint.h?

Tags:

c

macros

stdint

Please see my code:

#include <stdint.h>

int main(int argc, char *argv[])
{
unsigned char s = 0xffU;
char ch = 0xff;
int val = 78;
((int8_t) + (78)); /*what does this mean*/

INT8_C(val);    /*equivalent to above*/

signed char + 78; /*not allowed*/

return 0;
}

I find that the macro definition in <stdint.h> is:

#define INT8_C(val) ((int8_t) + (val))

What is the meaning or significance of this plus sign?

like image 973
Tracy Avatar asked Oct 04 '11 02:10

Tracy


3 Answers

The snippet:

((int8_t) + (78));

is an expression, one that takes the value 78, applies the unary +, then casts that to an int8_t type, before throwing it away. It is no real different to the legal expressions:

42;
a + 1;

which also evaluate the expressions then throw away the result (though these will possibly be optimised out of existence if the compiler can tell that there are no side effects).

These "naked" expressions are perfectly valid in C and generally useful only when they have side effects, such as with i++, which calculates i and throws it away with the side effect being that it increments the value.

The way you should be using that macro is more along the lines of:

int8_t varname = INT8_C (somevalue);

The reason for the seemingly redundant unary + operator can be found in the standard. Quoting C99 6.5.3.3 Unary arithmetic operators /1:

The operand of the unary + or - operator shall have arithmetic type;

And, in 6.2.5 Types, /18:

Integer and floating types are collectively called arithmetic types.

In other words, the unary + prevents you from using all the other data types in the macro, such as pointers, complex numbers or structures.

And, finally, the reason your:

signed char + 78;

snippet doesn't work is because it's not the same thing. This one is starting to declare a variable of type signed char but chokes when it gets to the + since that's not a legal variable name. To make it equivalent to your working snippet, you would use:

(signed char) + 78;

which is the casting of the value +78 to type signed char.

And, as per C99 7.8.14 Macros for integer constants /2, you should also be careful with using non-constants in those macros, they're not guaranteed to work:

The argument in any instance of these macros shall be an unsuffixed integer constant (as defined in 6.4.4.1) with a value that does not exceed the limits for the corresponding type.

6.4.4.1 simply specifies the various integer formats (decimal/octal/hex) with the various suffixes (U, UL, ULL, L, LL and the lower-case equivalents, depending on the type). The bottom line is that they have to be constants rather than variables.

For example, glibc has:

# define INT8_C(c)      c
# define INT16_C(c)     c
# define INT32_C(c)     c
# if __WORDSIZE == 64
#  define INT64_C(c)    c ## L
# else
#  define INT64_C(c)    c ## LL
# endif

which will allow your INT8_C macro to work fine but the text INT64_C(val) would be pre-processed into either valL or valLL, neither of which you would want.

like image 112
paxdiablo Avatar answered Nov 16 '22 20:11

paxdiablo


Seems everyone has missed the point of the unary plus operator here, which is to make the result valid in #if preprocessor directives. Given:

#define INT8_C(val) ((int8_t) + (val))

the directives:

#define FOO 1
#if FOO == INT8_C(1)

expand as:

#if 1 == ((0) + (1))

and thus work as expected. This is due to the fact that any un#defined symbol in an #if directive expands to 0.

Without the unary plus (which becomes a binary plus in #if directives), the expression would not be valid in #if directives.

like image 31
R.. GitHub STOP HELPING ICE Avatar answered Nov 16 '22 20:11

R.. GitHub STOP HELPING ICE


It's a unary + operator. It yields the value of its operand, but it can be applied only to arithmetic types. The purpose here is to prevent the use of INT8_C on an expression of pointer type.

But your statement expression

INT8_C(val);

has undefined behavior. The argument to INT8_C() is required to be "an unsuffixed integer constant ... with a value that does not exceed the limits for the corresponding type" (N1256 7.18.4). The intent of these macros is to let you write something like INT64_C(42) and have it expand to 42L if int64_t is long, or to 42LL if int64_t is `long long, and so forth.

But writing either

((int8_t) + (78));

or

((int8_t) + (val));

in your own code is perfectly legal.

EDIT:

The definition for INT8_C() in the question:

#define INT8_C(val) ((int8_t) + (val))

is valid in C99, but is non-conforming according to the later N1256 draft, as a result of a change in one of the Technical Corrigenda.

The original C99 standard, section 7.18.4.1p2, says:

The macro INT***N*_C**(value) shall expand to a signed integer constant with the specified value and type int_least***N*_t**.

N1256 changed this to (paragraph 3):

Each invocation of one of these macros shall expand to an integer constant expression suitable for use in #if preprocessing directives. The type of the expression shall have the same type as would an expression of the corresponding type converted according to the integer promotions. The value of the expression shall be that of the argument.

The change was made in response to Defect Report #209.

EDIT2: But see R..'s answer; it's actually valid in N1256, for quite obscure reasons.

like image 25
Keith Thompson Avatar answered Nov 16 '22 21:11

Keith Thompson