- C
unsigned int a = 1; signed int b = -1;
if (a<b) printf("%d<%d\n",a,b);
else printf("%d>=%d\n",a,b);
Expected result: 1>=-1
Result: 1<-1
Reason: According to the usual arithmetic conversions specified in C11 §6.3.1.8, operand b
is implicitly converted to an expression of type unsigned int
and thus evaluates to a large value.
Tested with: gcc 6.3.0, clang 3.9
The &&
and ||
operators can be used in a statement like in Perl:
(i>1) && printf("more than one argument\n");
Since var[i] == *(var + i) == *(i + var)
, C accepts the notation i[var]
as
equivalent:
const char *hello = "hello, world";
int i = 4;
printf("char %d: %c\n", i, i[hello]);
printf("char %d: %c\n", i, i["hello, world"]);
The #include
preprocessor directive can be used to interactively ask user a
value during the compilation:
int i =
#warning Enter value for i:
#include "/dev/tty"
;
Although illegal in standard C, a GNU extension makes it possible to omit the middle operand in a conditional expression when using the ternary operator. If the condition evaluates to true, the value of the conditional expression is then that of the first operand, i.e. the value of the condition itself. The following two expressions are thus equivalent in GNU dialect of ISO C (e.g. when compiling with -std=gnu89
):
x ?: y
x ? x : y
This extension is particularly useful when the evaluation of the condition has side effects, as it avoids performing them twice. However, this can make ternary conditions more difficult to read:
/* From Linux v5.4-rc5 (https://elixir.bootlin.com/linux/v5.4-rc5/source/drivers/gpu/drm/i915/i915_active.c#L80) */
return (void *)ref->active ?: (void *)ref->retire ?: (void *)ref;
/* From Linux v5.4-rc5 (https://elixir.bootlin.com/linux/v5.4-rc5/source/net/rxrpc/local_object.c#L39) */
diff = ((local->srx.transport_type - srx->transport_type) ?:
(local->srx.transport_len - srx->transport_len) ?:
(local->srx.transport.family - srx->transport.family));
and even more prone to type errors, as the condition must be type-compatible with the third operand.
char *hello = "hello, world";
hello[0] = 'Y';
hello[1] = 'o';
Expected result: warning or error
Result: no warning with -Wall -Wextra -pedantic
, segmentation fault at runtime
Reason: For historical reasons, string literals are of type char[]
in C but
attempting to modify such an array is undefined behavior (C11 §6.4.5). In
practice, most compilers place string literals in the .rodata
section,
therefore hello
cannot be modified and this code is expected to always
segfault. Adding the -Wwrite-strings
flag shows the problem, but this flag is
not in -Wall -Wextra -pedantic
.
Tested with: gcc 6.3.0, clang 3.9
The size of a char
is implementation defined. On some architectures it will be signed, and unsigned on others.
Reference: ISO C99 section 6.2.5
Solution: use stdint.h
types
The definition of the representation of integers is the following:
The rank of long long int shall be greater than the rank of long int, which
shall be greater than the rank of int, which shall be greater than the rank of short
int, which shall be greater than the rank of signed char.
Technically, a compiler with 17-bit integers is compliant with the specifications.
The same is also true for long
and long long
types.
Note that, unlike char
, the integer types are all signed
by default.
Reference: ISO C99 section 6.3.1.1
Solution: use stdint.h
types
Shifting by a negative number of a number of bits greater than the size of the type is undefined.
unsigned long setBit1(int n)
{ return (1 << n); }
unsigned long setBit2(int n)
{ return ((unsigned long) 1 << n); }
void main () {
printf ("%lu\n", setBit1 (33));
printf ("%lu\n", setBit2 (33));
}
Reference: ISO C99 section 6.5.7
Solution: don't do that
The expressions passed as arguments of a function call are evaluated in an unspecified order.
Example 1:
{ int c=0; printf("%d %d\n",c++,c++); }
{ int c=0; printf("%d %d\n",++c,++c); }
{ int c=0; printf("%d %d\n",c=1,c=2); }
Results can be surprising:
- The first line prints
1 0
- The second line prints
2 2
- The third line prints
1 1
Example 2:
int f(int x,int y) { return (2*x)+y; }
int main(void) {
int x=0;
int y=f((x=1),(x=3));
printf("x=%d\n",x);
printf("y=%d\n",y);
return 0;
}
Output:
x=1
y=3
Meaning that x=1
was executed after x=3
and passed as both arguments of function f
.
Reference: ISO C99 section 6.5.2.2
Solution: don't do that