Main Content

CERT C: Rec. DCL21-C

Understand the storage of compound literals

Since R2024b

Description

Rule Definition

Understand the storage of compound literals1

Polyspace Implementation

The rule checker checks for Address of compound literal stored

Examples

expand all

Issue

This rule checker reports a violation when the address of an unnamed temporary variable that contains the result of a compound literal is either:

  • Assigned to a pointer

  • Returned by a function

A compound literal is a parenthesized type name followed by an initializer list. For example, (int[2]) {56, 37} is a compound literal. The value of this expression is an unnamed temporary integer array consisting of the two elements 56 and 37. If you assign the address of this temporary object to a pointer or if you return the address of the temporary object from a function, Polyspace® reports a violation of this rule.

void foo() {
	int *ptr = (int[2]) {56, 37}; // Noncompliant
    //...
}

Risk

Using pointers to compound literals can lead to undefined or unexpected behaviors:

  • The lifetime of the compound literal ends at the end of the block where it occurs. Attempting to access the compound literal after its lifetime has ended results in undefined behavior. Consider this code:

    const char *getChar() {
    
    	return "F";
    }
    
    const char *getCharFrom() {
    
    	return (char[]) {'F','\0'}; // NonCompliant
    }
    
    void func() {
    	puts(getChar());
    	puts(getCharFrom());   // Undefined Behavior
    }
    The function getCharFrom() returns a pointer that points to the address of an unnamed temporary object. The temporary object no longer exists when the function returns. In func(), calling getCharFrom() results in accessing an object after its lifetime ends, which is undefined behavior.

    Note that returning a compound literal that evaluates to C-string is different from returning a string literal, which copies the string literal to the returning function. Calling the getChar() function in func() is compliant.

  • If a compound literal appears within a loop, only one object is created and it is overwritten in each iteration of the loop. If the programmer is unaware of this behavior, errors in the code can occur.. Consider this code:

    typedef struct Point {
    	double X;
    } Point;
    
    #define SIZE 10
    void foo () {
    
    	Point *Lines[SIZE];
    	for(int i = 0; i < SIZE; ++i) {
    		Lines[i] = &(Point) { i + 1}; // NonCompliant
    	}
    
    }
    Here, the compound literal (Point) { i + 1} creates one object. Each iteration of the for loop overwrites the same object. The lifetime of this object ends at the end of the for loop. Dereferencing this object later in your code is undefined behavior.

Fix

Avoid taking address of the temporary objects created by using compound literals. When using compound literals, copy the temporary object into a named object with an appropriate lifetime.

Example

In this example, the array Lines contains pointers to Point objects. In the function foo(), a compound literal is used in the for loop to construct a temporary Point object and its address is assigned to the elements of Lines. Taking the address of the temporary object resulting from the compound literal is a violation of this rule. Because the compound literal creates one object that is overwritten in each iteration of the loop, the elements of Lines all point to the same object. In addition, the object pointed to by the elements of Lines goes out of scope at the end of the for loop. Accessing this object in subsequent code is undefined behavior.

#include <stdio.h>

typedef struct Point {
	int X;
} Point;
#define SIZE 10
void foo() {

	Point *Lines[SIZE];
	for(int i = 0; i < SIZE; ++i) {
		Lines[i] = &(Point) {i}; //Noncompliant
	}
	for(int i = 0; i < SIZE; ++i) {
		printf("Xs are: %d\n", Lines[i]->X);
	}

}

Correction

To fix this issue, avoid taking the address of temporary objects. For example, in this code, Point objects are created inline using compound literals, but the resulting temporaries are copied into the Lines array. As a result, each element of Lines contains a different object and each element of Lines has a lifetime that is the same as the array. Polyspace does not report a violation on this code.

#include <stdio.h>

typedef struct Point {
	int X;
} Point;
#define SIZE 10
int main() {

	Point Lines[SIZE];
	for(int i = 0; i < SIZE; ++i) {
		Lines[i] = (Point) {i}; //Compliant
	}
	for(int i = 0; i < SIZE; ++i) {
		printf("Xs are: %d\n", Lines[i].X);
	}

}

Check Information

Group: Rec. 02. Declarations and Initialization (DCL)

Version History

Introduced in R2024b


1 This software has been created by MathWorks incorporating portions of: the “SEI CERT-C Website,” © 2017 Carnegie Mellon University, the SEI CERT-C++ Web site © 2017 Carnegie Mellon University, ”SEI CERT C Coding Standard – Rules for Developing safe, Reliable and Secure systems – 2016 Edition,” © 2016 Carnegie Mellon University, and “SEI CERT C++ Coding Standard – Rules for Developing safe, Reliable and Secure systems in C++ – 2016 Edition” © 2016 Carnegie Mellon University, with special permission from its Software Engineering Institute.

ANY MATERIAL OF CARNEGIE MELLON UNIVERSITY AND/OR ITS SOFTWARE ENGINEERING INSTITUTE CONTAINED HEREIN IS FURNISHED ON AN "AS-IS" BASIS. CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY KIND, EITHER EXPRESSED OR IMPLIED, AS TO ANY MATTER INCLUDING, BUT NOT LIMITED TO, WARRANTY OF FITNESS FOR PURPOSE OR MERCHANTABILITY, EXCLUSIVITY, OR RESULTS OBTAINED FROM USE OF THE MATERIAL. CARNEGIE MELLON UNIVERSITY DOES NOT MAKE ANY WARRANTY OF ANY KIND WITH RESPECT TO FREEDOM FROM PATENT, TRADEMARK, OR COPYRIGHT INFRINGEMENT.

This software and associated documentation has not been reviewed nor is it endorsed by Carnegie Mellon University or its Software Engineering Institute.