C Programming Language Quiz
This quiz is about quirks of the programming language C and intended for fun and educational purpose. The one or the other question is more academic, i.e., it should make you think ;-)
Unless otherwise stated, the questions and corresponding answers are independent of a specific version of the C standard. Thus the answers are the same considering C89 up to and including C17 and probably future releases.
-
If two pointers
pandqof the same type point to the same address, thenp == qmust evaluate to true.YesNoShort answer: Comparing two pointers which are derived from two different objects which are not part of the same aggregate or union object invokes undefined behavior.
Have a look at this post for a detailed discussion.
-
Consider the following code snippet where an object of type
intis accessed through lvalues of typeshortandunsigned char:int i = 42; short *s = (short *) &i; unsigned char *c = (unsigned char *) &i; *s; // (A) *c; // (B)(A)and(B)invoke undefined behavior(A)invokes undefined behavior but(B)is legal(B)invokes undefined behavior but(A)is legal(A)and(B)are legalThe rule of thumb is: accessing an object of type
Tthrough an lvalue of typeUwhereTandUare not compatible (modulo few exceptions) invokes undefined behavior—according to the strict aliasing rules. That means, in the example we accessed an object of typeintthrough an lvalue of typeshortwhich leads to undefined behavior. One exception to the rule is that a character pointer may alias any other pointer, i.e., any object may be accessed through a character pointer.Note, only type
unsigned charis guaranteed to have no padding bits and therefore has no trap representation which could invoke undefined behavior (since C11 alsosigned charis guaranteed to have no padding bits).Thus
(A)invokes undefined behavior whereas(B)is legal.Have a look at this post for a detailed discussion.
-
It may make a difference whether an integer constant is given in decimal or hexadecimal format. In other words the meaning may be different.
YesNoIn a nutshell the type of an unsuffixed decimal constant is always signed whereas of a hexadecimal constant the type may be signed or unsigned.
C17 § 6.4.4.1 Integer constants ¶ 5
The type of an integer constant is the first of the corresponding list in which its value can be represented.
Suffix Decimal Constant Octal or Hexadecimal Constant none intlong intlong long intintunsigned intlong intunsigned long intlong long intunsigned long long intThus for constants between
INT_MAX+1andUINT_MAXthe type differs depending on whether the constant is given in decimal or hexadecimal format. In certain cases this might lead to unexpected effects. One example are functions with a variable number of arguments.void foo(int n, ...); void bar(void) { foo(42, 0x80000000); foo(42, 2147483648); }Depending on the ABI and width of integer types of a platform, the function calls may differ. For example, according to the ABI for the Arm 32-bit architecture an
intandlongare 32-bit wide and passed in one register whereas along longis 64-bit wide and passed in two registers. Thus the call sides differ.bar: push {r3, lr} // first call side // second argument is in r1 only mov r1, #-2147483648 movs r0, #42 bl foo // second call side // second argument is in r2 and r3 mov r2, #-2147483648 movs r3, #0 movs r0, #42 bl foo pop {r3, pc}Of course, there are plenty examples about arithmetic expressions which suffer from this nuance as e.g.
uint64_t x = 0x80000000 << 1; uint64_t y = 2147483648 << 1;where
xequals zero andyequals 232 assuming thatintis 32-bit wide.We may not only run into different behavior on the same platform but also across which results in portability problems. Arithmetic expressions may lead to different results on different platforms. For example, expression
-1 < 0x8000evaluates totrueon a platform whereintis 32-bit wide and tofalsewhereintis 16-bit wide.A further example where the type of a constant makes a difference are generic selections:
#define print_type_of(x) _Generic((x), \ unsigned int: puts("unsigned int"), \ long: puts("long")) print_type_of(0x80000000); print_type_of(2147483648);While speaking of generic selections the whole story may also lead to subtle situations in C++ where we have overloaded functions:
void foo(unsigned int x); void foo(long x); void bar() { foo(0x80000000); foo(2147483648); }Another, probably more contrived example, is
sizeof(0x80000000) == sizeof(2147483648)which evaluates tofalseon a platform whereintis 32-bit wide. -
The following two function declarations can be used interchangeably, i.e., they mean exactly the same:
int foo(); int foo(void);YesNoThe short answer is that the former declares a function with an unknown number and types of arguments while the latter declares a function without any argument, i.e., it is a nullary function.
Have a look at this post for a detailed discussion.
-
The following function declaration in conjunction with the function definition is legal.
void foo(); void foo(int a) { }YesNoThat is a valid function declaration and definition. The declaration only introduces the function name
foowithout defining the number and types of arguments.Have a look at this post for a detailed discussion.
-
The following function declaration in conjunction with the function definition is legal.
void foo(); void foo(float a) { }YesNoThis is not a valid function declaration in conjunction with the definition. The short answer is that whenever a function is called where no prototype is available, then the default argument promotions are applied. For example, a
floatis promoted to adouble. In this case, the function type is not compatible with the function type after the default argument promotions.Have a look at this post for a detailed discussion.
-
Consider function
foowhich does not return a value although the return type isint.int foo() { }Now consider two statements
(A)and(B)which call functionfoo, respectively.foo(); // (A) foo() + 42; // (B)(A)and(B)invoke undefined behavior(A)invokes undefined behavior but(B)is legal(B)invokes undefined behavior but(A)is legal(A)and(B)are legalThis is an artifact from the old days. In K&R C there exists no type
void. Furthermore, in case no type was given, default typeintwas assumed. Thus, the following functions are equivalent in K&R C and both do not return a value:f() { } int g() { }The implicit
intrule was abandoned in C99 (see discussion N661 or C99 rationale). However, for the sake of downward compatibility, it is still legal to return no value. Though, using the value of the call invokes undefined behavior.C17 § 6.9.1 Function definitions ¶ 12
If the
}that terminates a function is reached, and the value of the function call is used by the caller, the behavior is undefined.Fun fact: C++ differs in the sense that value-returning functions must return a value—independent whether the value of a call is used or not.
C++98 § 6.6.3 The
returnstatement ¶ 2[…] Flowing off the end of a function is equivalent to areturnwith no value; this results in undefined behavior in a value-returning function.Note, what may seem counterintuitive at first glance is that a C++ compiler may in general only emit diagnostics in such cases and not treat them as errors. The following example illustrates why this is the case.
template<typename T> T call_or_abort(std::function<T(void)> f) { if (f) return f(); else abort_program(); }First of all there is no way to express a value of type
Twhich could be returned in case theelsebranch is taken. Furthermore, in general it is not possible to prove whether function callabort_program()terminates or not. Thus, throwing an error would reject possibly valid C++ programs which is the reason why only a diagnostics is emitted, if at all. -
Consider the following two declarations of function
foo.static void foo(void); extern void foo(void);Which linkage does function
foofinally have?Internal linkageExternal linkageCode invokes undefined behaviorThis is defined in the following paragraph:
C17 § 6.2.2 Linkages of identifiers ¶ 4
For an identifier declared with the storage-class specifier
externin a scope in which a prior declaration of that identifier is visible,31) if the prior declaration specifies internal or external linkage, the linkage of the identifier at the later declaration is the same as the linkage specified at the prior declaration. If no prior declaration is visible, or if the prior declaration specifies no linkage, then the identifier has external linkage.31) As specified in 6.2.1, the later declaration might hide the prior declaration.
Fun fact: the other way around
extern void foo(void); static void foo(void);invokes undefined behavior which is caught by GCC as well as Clang:
test.c:2:13: error: static declaration of ‘foo’ follows non-static declaration 2 | static void foo(void); | ^~~ test.c:1:13: note: previous declaration of ‘foo’ was here 1 | extern void foo(void); | ^~~ -
Function parameter
xisconst-qualified in the function declaration but not in the function definition. Furthermore, parameterxis even written to in the function body. Is this legal?void foo(int const x); void foo(int x) { x = 42; }YesNoType qualifiers are not considered while determining if two function parameters are compatible.
C11 § 6.7.6.3 Function declarators (including prototypes) ¶ 15
[…] In the determination of type compatibility and of a composite type, […] each parameter declared with qualified type is taken as having the unqualified version of its declared type. […]This was also asked in DR040.
-
The return type for function
fooisconst-qualified in the function definition but not in the function declaration. Is this legal?int foo(void); int const foo(void) { return 42; }YesNoThe answer to this question is neither right nor wrong. The overall consensus is that qualifiers for rvalues should be ignored. Though, strictly speaking the wording of the C standard up to and including C11 do not address this.
In C17 it was made explicit that type qualifiers should be ignored for rvalues, i.e., in particular for casts (C17 § 6.5.4 ¶ 5), lvalue conversion (C17 § 6.5.1.1 ¶ 2), and for function declarators:
C17 § 6.7.6.3 Function declarators (including prototypes) ¶ 5
If, in the declaration “
T D1”,D1has the formD ( parameter-type-list )or
D ( identifier-listopt )and the type specified for ident in the declaration “
T D” is “derived-declarator-type-listT”, then the type specified for ident is “derived-declarator-type-list function returning the unqualified version ofT”.The words “the unqualified version of” were added in C17 which have a greater impact than one might first suspect. Consider the following code:
int const foo(void) { return 42; } void bar(void) { int (*f)(void) = foo; int const (*g)(void) = f; }Both assignments are legal, although the return types of the function types are differently
const-qualified. -
Consider the following translation unit consisting of a global variable declaration:
struct foo x; struct foo { int i; }At the point of the declaration of variable
xthe structure typefoois incomplete. Thus the size is unknown. Only later on in the translation unit the type is completed. Is this translation unit legal?YesNoThis is allowed in certain circumstances as e.g. for global variables. A similar argument holds for arrays with an incomplete type.
int array[]; int array[42];This is also mentioned in DR016.
-
Declaring a variable of type
voidwith internal linkage is not legal.static void foo;Is it legal to declare a variable of type
voidwith external linkage?extern void foo;YesNoAccording to the grammar this is legal. Furthermore, it is nowhere explicitly stated in the C11 standard that it is not allowed. However, I cannot think of any real use case and therefore assume that this is only allowed by accident. Note, type
voidis incomplete and cannot be completed.C11 § 6.2.5 Types ¶ 19
The
voidtype comprises an empty set of values; it is an incomplete object type that cannot be completed.Thus the only operation which comes to my mind is to take the address of variable
foo. However, it turns out that expressionfoois not a valid lvalue:C11 § 6.3.2.1 Lvalues, arrays, and function designators ¶ 1
An lvalue is an expression (with an object type other thanvoid) that potentially designates an object […]It is unclear to me whether taking the address of such objects was legal in the past or not, since the definition of lvalues changed between standard revisions. At least for C11 I cannot think of any meaningful and conforming operation on external objects of type
void.The whole story is also discussed in DR012. There it has been pointed out that it is legal to take the address of object
fooif the type is changed fromvoidtoconst void. Again this looks more like an oversight to me than something which was intended. -
The bit representation of the null pointer must have all bits zero.
YesNoThe null pointer constant is defined by the C standard. However, the representation of the null pointer at run-time is not. Actually, the representation of pointers in general is not defined by the C standard. For example, the Symbolics Lisp Machine 3600 does not make use of numerical pointers at all but of tuples of the form
<array-object, index>. The representation of the null pointer is then<nil, 0>. Have a look at clc FAQ 5.17 for further examples. -
Constant
0evaluates to an integer or the null pointer—depending on the context.int i = 0; int *p = 0;YesNo -
The expression
(void *)0evaluates to the null pointer.YesNo -
If the expression
eevaluates to0, then it is guaranteed that(void *)eevaluates to the null pointer.YesNoOnly the null pointer constant converted to pointer type is guaranteed to equal the null pointer.
-
If the expression
eevaluates to the null pointer, then it is guaranteed thate+0evaluates to the null pointer, too.YesNoNull pointer arithmetic is undefined behavior in C.
-
Is it possible that function
foois called?int x; if (x != x) foo();YesNoShort answer: Yes, it is possible because variable
xis uninitialized when read.Long answer: the wording of the C standard has changed over time w. r. t. uninitialized variables, indeterminate values, and so on. Therefore, let us consider C11 for the moment which received the following update:
C11 § 6.3.2.1 Lvalues, arrays, and function designators ¶ 2
[…] If the lvalue designates an object of automatic storage duration that could have been declared with theregisterstorage class (never had its address taken), and that object is uninitialized (not declared with an initializer and no assignment to it has been performed prior to use), the behavior is undefined. […]Thus according to the paragraph the code snippet invokes undefined behavior and therefore anything may happen including a function call to
foo.Why the special treatment of objects which may have
registerstorage class? This constraint can be traced back to the Intel Itanium architecture (see DR338) which has general integer registers with 64-bit plus one trap bit. The trap bit indicates whether the register is initialized or not and is referred to asNaT, “not-a-thing”. Reading a register where theNaTbit is set may raise an exception. -
Now consider a slightly modified version of the previous question. Is it possible that function
foois called?int x; &x; if (x != x) foo();YesNoIn the previous example it is possible that function
foois called because the code invokes undefined behavior according to C11 section 6.3.2.1 paragraph 2. This time the address of variablexis taken and therefore the paragraph does not apply anymore.The value of variable
xis indeterminate, i.e., it is either a trap representation or an unspecified value. In the former case, reading a trap representation invokes undefined behavior (C11 § 6.2.6.1 ¶ 5) and therefore anything may happen including a function call tofoo. In the latter case, if the value ofxis unspecified, the expressionx != xevaluates to an unspecified value, too. Thus the expression may evaluate totrueorfalsewhich means that functionfoomay be called.Have a look at DR260 or DR451 for further discussions of indeterminate and unspecified values. The prevailing opinion is that the value of an expression is indeterminate (unspecified) if a subexpression evaluates to an indeterminate (unspecified) value. For example, if
xof typeintis unspecified, thenx *= 0is unspecified, too. In particular this means it is not guaranteed thatxequals zero after the assignment. Furthermore, unspecified values may be “unstable” or sometimes also referred to as “wobbly”.unsigned char x; printf("%i\n", x); printf("%i\n", x);In the example it is not guaranteed that the same value is printed twice. Thus, the value of
xmay change between the function calls. For further discussions have a look at N1793, N1818, N2012, N2013, N2221. -
Again, consider a slightly modified version of the previous question. Is it possible that function
foois called?unsigned char x, y; memcpy(&x, &y, 1); if (x != x) foo();YesNoThe type
unsigned charis guaranteed to have no trap representations (C11 § 6.2.6.1 ¶ 3). Thus the initial value ofxis unspecified.According to an answer from a C committee member on StackOverflow the value of
xshould be specified after the call to the standard library functionmemcpy. Thus the expressionx != xshould evaluate tofalse. I'm saying should because I cannot find any evidence for that in the C standard. Having a look at DR451 the committees response islibrary functions will exhibit undefined behavior when used on indeterminate values
which contradicts with the answer on StackOverflow. Therefore, I leave this question open.Have a look at Uninitialized Reads for further discussions.
-
Let
Tbe a (derived) object type. The assignment ofcpis legal. Is the assignment ofcpplegal, too?T *p = /* something */; T **pp = /* something */; T const *cp = p; T const **cpp = pp;YesNoFor this question I do not have a short answer. Have a look at this post for a detailed discussion.
-
The expression
sizeof(int) > -1evaluates totruefalseThe short answer is that operator
sizeofreturns an unsigned integer of typesize_t. According to the usual arithmetic conversions (C11 § 6.3.1.8) the operand which is signed and has a lower rank than the unsigned operand, is converted to an unsigned integer type of the same rank as the unsigned operand. Any signed integer which is equivalent to-1has all bits set. Such a sequence of bits interpreted as an unsigned integer equals the maximal unsigned integer of each corresponding integer rank. In other words(unsigned int)-1equalsUINT_MAX. The same holds true for all other integersshort int,long int, andlong long int. Thus, the left operand of the relational operator evaluates to the maximal unsigned integer representable by an unsigned integer whose rank is equivalent to the rank ofsize_t. Since there exists no unsigned integer with a same rank which is strictly greater, the expression always evaluates tofalse. -
The following two statements are equivalent.
char x[] = "123"; char *x = "123";YesNoThe first assignment initializes an array with automatic or static storage duration (depends on whether
xis declared in file or function scope) which is modifiable. Whereas the second assignment initializes a pointer to an array with static storage duration which is not necessarily modifiable. Or in other words arrays are not pointers.Have a look at this post for a detailed discussion.
-
Let
int a[42];be an array, then all three expressionsaand&aand&a[0]evaluate to a pointer to the first element of the array, i.e., the following equalities hold:(void *) a == (void *)&a (void *) a == (void *)&a[0] (void *)&a == (void *)&a[0]Does this mean that all three expressions mean exactly the same and therefore can be used interchangeably?
YesNoAll three expressions evaluate to a pointer to the first element of the array. However, each expression has a different type. Have a look at this post for a detailed discussion.
-
Assume that the variables
a,b, andcare initialized before read.uint8_t a, b, c; /* ... */ uint8_t x = (a + b) / c; uint8_t y = a + b; uint8_t z = y / c;The values for the variables
xandzmay differ, i.e.,x == zmay evaluate tofalse.are always the same, i.e.,x == zalways evaluates totrue.Short answer: The integer promotions require that the value of each variable is promoted to size
int, then the addition and division is performed. The resulting value is truncated and stored in the corresponding variable of each assignment, respectively. Provided that the addition may overflow the values forxandzmay not equal.Long answer: The modulo operation is not distributive over division. Lets illustrate the problem by an example. Assume
a=255;,b=1;, andc=2;, then for the first assignment we have that the value stored inxequals the expression((255 + 1) / 2) % 256which equals(256 / 2) % 256which equals128 % 256which finally equals128. For the second assignment we have that the value stored inyequals the expression(255 + 1) % 256which equals256 % 256which finally equals0. N.B. an overflow occurred which is defined behavior for unsigned integers. For the last assignment we then have that the value stored inzequals the expression(0 / 2) % 256which equals0 % 256which finally equals0. Thereforex == zis not always the case since128 != 0.Fun fact 1: The modulo operation is distributive over addition, i.e.,
(a + b) % x == [(a % x) + (b % x)] % xfor alla,b,x. Thus, if we change the division by an addition, then the values for the variablesxandzare always the same.Fun fact 2: We could change the first assignment to
uint8_t x = ((uint8_t)(a + b)) / c;. Then the values for the variablesxandzare always the same, too. -
Is the size of type
charthe same as the size of any character constant? That means, the expressionsizeof(char) == sizeof('x')for any character constant'x'evaluates totruefalseCharacter constants have type
int(C11 § 6.4.4.4 ¶ 10). Thus onlysizeof(int) == sizeof('x')is guaranteed to evaluate totrue.N.B. in terms of the C11 standard a character constant is either an integer character constant or a wide character constant. The former is a sequence of one or more multibyte characters. Thus 'abc' is a valid integer character constant where the representation is implementation-defined. If an integer character constant contains a single character, then its value equals the integer representation of an object of type
charwhich represents the very same single character. -
Consider the following code snippet:
int x = 5, y = 7; int z = x+++y;The expression
x+++yleads to a compile error since operator+++is not definedinvokes undefined behavioris equivalent to(x++) + yis equivalent tox + (++y)In C you cannot define new operators like in C++. Thus such wired locking operators are a combination of existing operators. For example,
--*--is not a new operator but a valid combination of operators in the following code snippet:int x[1] = { 0 }; int *p = &x[1]; --*--p;Expression
--*--pis equivalent to--(*(--p))which evaluates to-1and as a side-effect assigns-1tox[0]. -
Consider the following code snippet:
int x; int y = (x=1) + (x=2);Code invokes undefined behavior.Code is legal and the value of variableyequalsOperator precedence is well defined. However, the order of evaluation of arithmetic operands is undefined. Thus, the expression
(x=1) + (x=2)invokes undefined behavior, i.e., it is undefined whether variablexshould equal1or2after the assignments. This renders the whole code as undefined.Fun fact: GCC 8.2.1 evaluates the expression to
4and Clang 7.0.0 to3while using options -std=c11 -O2. -
Consider the following code snippet:
int x; int y = (x=1) && (x=2);The value of variable
yequalsanything because the code invokes undefined behaviortruefalseOperator precedence is well defined and for the logical operators
&&and||the order of evaluation of operands is well defined, too. With the words of the C standard: between the evaluation of the first and second operand there exists a sequence point. Thus in the example we have that firstx=1is evaluated which equalstrueand thenx=2is evaluated which also equalstruewhich renders the whole expressiontrue. -
Which of the following two compilation units compile?
// Compilation Unit A const int n = 42; int x[n]; // Compilation Unit B const int m = 42; void foo(void) { int y[m]; }Both compilation units compile.Compilation unitAcompiles but notB.Compilation unitBcompiles but notA.Both compilation units do not compile.Short answer: compilation unit A does not compile because global array
xis a variable length array which is not allowed at file scope. However, a variable length array is allowed at block scope and therefore compilation unit B compiles.For the long answer we first look up the definition of an integer constant expression:
C11 § 6.6 Constant expressions ¶ 6
An integer constant expression117) shall have integer type and shall only have operands that are integer constants, enumeration constants, character constants,
sizeofexpressions whose results are integer constants,_Alignofexpressions, and floating constants that are the immediate operands of casts. Cast operators in an integer constant expression shall only convert arithmetic types to integer types, except as part of an operand to thesizeofor_Alignofoperator.117) An integer constant expression is required in a number of contexts such as the size of a bit-field member of a structure, the value of an enumeration constant, and the size of a non-variable length array. Further constraints that apply to the integer constant expressions used in conditional-inclusion preprocessing directives are discussed in 6.10.1.
Thus, the expressions
nandmwhich are used while declaring arraysxandy, respectively, are not integer constant expressions—althoughnandmare both variables which areconst-qualified. If the size expression of an array declaration is not an integer constant expression we have the following:C11 § 6.7.6.2 Array declarators ¶ 4
[…] If the size is an integer constant expression and the element type has a known constant size, the array type is not a variable length array type; otherwise, the array type is a variable length array type. […]Thus both arrays
xandyare of type variable length array for which we have:C11 § 6.7.6.2 Array declarators ¶ 2
If an identifier is declared as having a variably modified type, it shall be an ordinary identifier (as defined in 6.2.3), have no linkage, and have either block scope or function prototype scope. If an identifier is declared to be an object with static or thread storage duration, it shall not have a variable length array type.
Strictly speaking variable length arrays are a feature that implementations need not support and therefore there might exist a compiler which does not compile unit B, too. However, the takeaway message is that you cannot make use of variables in constant expressions although they are
const-qualified. You might want to replace a variable with a preprocessor macro, but then you lose all type information, which has some drawbacks, too.Fun fact: In C++ both compilation units compile fine. Though, C++ does not have the concept of variable length arrays and therefore
yis compiled into an ordinary array consisting of 42 elements. -
Consider the following function which includes a
switchstatement:void foo(int x) { int i = 0; switch (x) { printf("-1"); case 0: printf("0"); if (0) { case 1: printf("1"); } else { for (i = -1; i < x; ++i) { case 2: printf("2"); } } case 3: printf("3"); } }Code does not compileCode compiles but invokes undefined behaviorCode is legal and after three consecutive function callsfoo(0); foo(1); foo(2);the console output equals02313223The body of a switch statement may be an arbitrary statement which renders the given code legal. Before we go on let us have a look at some oddities.
Although the controlling expression of the
ifstatement always evaluates tofalsethecaselabel inside the body of thetruebranch renders it live, i.e., the statementprintf("1");is not dead code.Another interesting point is that the clause-1 of the loop as well as the controlling expression must not be executed, if
case 2is jumped to. Thus, it is crucial that variableiis initialized upfront.A further oddity is that although
case 1has nobreakstatement and therefore falls through, it “jumps” overcase 2and continues atcase 3becausecase 1is contained in thetruebranch andcase 2is contained in thefalsebranch of theifstatement.Thus we have the following console output produced by the respective function calls:
foo(0) --> 023 foo(1) --> 13 foo(2) --> 223A famous real world example of a mixed loop and switch statement is termed Duff's device.
-
Consider the following function where the type of parameter
xas well as of local variableyis an array of 42 integers.bool foo(int x[42]) { int y[42]; return sizeof(x) == sizeof(y); }The return value of function
fooequalstruefalseAny parameter of type “array of
T” is adjusted to a “pointer toT”. Thus the example is equivalent to the following:bool foo(int *x) { int y[42]; return sizeof(x) == sizeof(y); }For
sizeof(y)we have that it equals42 * sizeof(int). In general, the size of an object pointer does not equal 42 times the size of an integer. Thus,sizeof(x) == sizeof(y)evaluates tofalsein the general case.Have a look at this post for a detailed discussion.
-
Assume that a compilation unit only consists of the definition of function
fooand nothing else.void foo(void) { bar(42); }Inside of function
fooanother functionbaris called for which no declaration is given.Code does not compileCode compiles and is legal ifbaris a unary function and the type of the parameter is ashortCode compiles and is legal ifbaris a unary function and the type of the parameter is aintCode compiles and is legal ifbaris a unary function and the type of the parameter is alongThe code compiles since in C implicit functions are allowed—in contrast to C++ where this is a compile-time error.
Although integer
42is guaranteed to be representable by all integer types—including all character types—the only legal type for the parameter isint. This is the case since no function declaration forbaris given and therefore the integer argument promotions are applied (see C11 § 6.5.2.2 Function calls ¶ 6) which means that number42is represented by anint. Thus if type of functionbaris not compatible withT (*)(int)for some return typeT, then the example invokes undefined behavior. -
Consider the following code snippet:
struct X { int a[5]; } f(void); int *p = f().a; printf("%p\n", (void *)p);According to one of the following three standards the code invokes undefined behavior. Which one?
C90C99C11The lifetime of certain objects has been reduced in C11. In the example, the object returned by the function call is only alive until the right-hand side is evaluated in C11 whereas in C99 it is alive until the enclosing block ends. Referring to an object outside its lifetime invokes undefined behavior.
An example which invokes undefined behavior in C99, too, is given in the following:
struct X { int a[5]; } f(void); int *p; { p = f().a; } printf("%p\n", (void *)p);The lifetime of an object with automatic storage duration is bounded to its nearest enclosing block in C99. Thus in line 4 we refer to an object which is not live anymore resulting in undefined behavior.
C11 § 6.2.4 Storage durations of objects ¶ 2
[…] If an object is referred to outside of its lifetime, the behavior is undefined. […]The following paragraph is new and is the reason why the example invokes undefined behavior in C11.
C11 § 6.2.4 Storage durations of objects ¶ 8
[…] Its lifetime ends when the evaluation of the containing full expression or full declarator ends. […]
For further discussion have a look at N1285 from which the code snippet was taken.
Score
You answered 0 out of 0 questions correctly!