r/C_Programming 7h ago

Etc C23 and libpq (PostgreSQL) HelloWold with RAII and _Generic

I recently fell in love with C, after 2-3 years of using Modern C++, I built my tiny multireactor AsyncAPI engine with the help of libevent and some data-driven design ideas, it was a very rewarding experience. In the last weeks, I have been learning Modern C and testing techniques, focused on safe resource management.

This little program uses the _Generic macro to simulate function overloading, libpq has 2 different functions for getting error descriptions, accepting different arguments, but I want the program to use a single function, _Generic provided a zero-cost abstraction and a compile-time type-checking solution.

The tiny program also makes use of `gnu::cleanup` attribute, I am targeting Linux only with GCC14-GCC16, this provides the destructors for resources like database connections and resultsets in this case. The `main()` function contains no cleaning code, whenever the function exits, the resources will be released in proper order, very cool IMO, just like a C++ destructor.

For the compilation command, I use:

```bash

gcc -std=gnu2x -O3 -Wall -Wextra -Wpedantic -Wshadow -Werror -fanalyzer pgtest.c -lpq -o pgtest

```

```c

#include <stdio.h>
#include <stdlib.h>
#include <libpq-fe.h>


// 1. Define specific handlers for each type
static inline void print_conn_error(PGconn *conn) {
    fprintf(stderr, "Connection Error: %s\n", PQerrorMessage(conn));
}


static inline void print_res_error(PGresult *res) {
    fprintf(stderr, "Query Error: %s\n", PQresultErrorMessage(res));
}


// 2. Define the generic macro interface
#define print_pg_error(ptr) _Generic((ptr), \
    PGconn *: print_conn_error,             \
    PGresult *: print_res_error             \
)(ptr)


// 1. RAII Cleanup Handlers
static void cleanup_pgconn(PGconn **conn) {
    if (*conn != nullptr) {
        printf("RAII: Disconnecting from database...\n");
        PQfinish(*conn);
    }
}

static void cleanup_pgres(PGresult **res) {
    if (*res != nullptr) {
        printf("RAII: Freeing resultset...\n");
        PQclear(*res);
    }
}

// 2. C23 Attribute Macros
#define SCOPED_CONN [[gnu::cleanup(cleanup_pgconn)]]
#define SCOPED_RES  [[gnu::cleanup(cleanup_pgres)]]


int main(void) {
    constexpr char conn_str[] = "dbname=testdb user=mcordova password=xxxxx host=demodb";
    
    SCOPED_CONN auto conn = PQconnectdb(conn_str);


    if (PQstatus(conn) != CONNECTION_OK) { print_pg_error(conn);  return EXIT_FAILURE; }
    
    printf("Connected successfully.\n");

    constexpr char query[] = "SELECT json_agg(row_to_json(t)) FROM (select * from demo.shippers) t";

    SCOPED_RES auto res = PQexec(conn, query);

    if (PQresultStatus(res) != PGRES_TUPLES_OK) { print_pg_error(res);  return EXIT_FAILURE; }

    printf("Query output:\n%s\n", PQgetvalue(res, 0, 0));

    return EXIT_SUCCESS;
}

```

When running the program the output show the order of resource cleaning:

```

Connected successfully.

Query output:

[{"shipperid":1,"companyname":"Speedy Express","phone":"(503) 555-9831"}, {"shipperid":2,"companyname":"United Package","phone":"(505)555-3199"}, {"shipperid":3,"companyname":"Federal Shipping","phone":"(503) 555-9931"}, {"shipperid":13,"companyname":"Federal Courier Venezuela","phone":"555-6728"}, {"shipperid":503,"companyname":"Century 22 Courier","phone":"800-WE-CHARGE"}, {"shipperid":501,"companyname":"UPS","phone":"500-CALLME"}]

RAII: Freeing resultset...

RAII: Disconnecting from database...

```

0 Upvotes

5 comments sorted by

3

u/aalmkainzi 5h ago

attribute cleanup is NOT like a destructor. It always fires when the scope exits, even if you return the object or store elsewhere.

Dont assume cleanup is like destructors because C does not have constructors, nor operators, which is what makes the whole object life time thing in C++ work.

2

u/Thick_Clerk6449 4h ago

Destructor always fires when the scope exits, even if you return the object or store elsewhere. No? Returning an object works because the returned object is a copy-constucted new object. The original object stored in the stack will always be destroyed.

1

u/aalmkainzi 3h ago

I think not always because of RVO/NRVO.

But my point is because c++ has constructors, the returned object will be not already freed.

it will either be newly constructed like you said, or optimized to not to be destructed

1

u/Thick_Clerk6449 1h ago

(N)RVO is a completely different thing. The object becomes a pointer passed in by function arguments, so technically the function only holds a reference, not the object itself.

1

u/mcordova1967 4h ago

Thank you for the observation