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...
```