My research focuses on




For large-scale simulations, it is hard to beat the speed and efficiency of C. But its standard library is minimal, lacking dynamic arrays or file parsing, which are common in Python. C++ improves container support, yet still falls short of Python-level convenience. The functions below fill some gaps, streamlining routine tasks and speeding up everyday workflows.
#define init_Array_1D(type, rows) ((type*)array_1d((rows), sizeof(type)))
void* array_1d(size_t rows, size_t element_size) {
return calloc(rows, element_size);
}
#define init_Array_2D(type, rows, cols) ((type**)array_2d((rows), (cols), sizeof(type)))
void** array_2d(size_t rows, size_t cols, size_t element_size) {
void **array = calloc(rows, sizeof(void*));
if (array == NULL) return NULL;
for (size_t i = 0; i < rows; i++) {
array[i] = calloc(cols, element_size);
if (array[i] == NULL) {
for (size_t j = 0; j < i; j++) free(array[j]);
free(array);
return NULL;
}
}
return array;
}
The functions above can declare, allocate, and initialize a memory block in a single statement, with all elements set to zero. They are called as double *h = init_Array_1D(double,n); or double **h = init_Array_2D(double,m,n); for a 1D or 2D array of type double, respectively. You can replace double with int or any other type, including custom data types created with typedef struct. The functions return NULL if allocation fails, so the caller should check the return value before use. As with any calloc allocation, remember to free the memory when it is no longer needed, and you can use the following companion functions for that.
#define FREE_ARRAY_1D(array) clean_1d((void *)(array))
void clean_1d(void *array) {
free(array);
array = NULL;
}
#define FREE_ARRAY_2D(array, rows) clean_2d((void **)(array), (rows))
void clean_2d(void **array, size_t rows) {
if (array == NULL) return;
for (size_t i = 0; i < rows; i++) free(array[i]);
free(array);
array = NULL;
}
__attribute__((format(printf, 1, 2)))
char* make_Name(const char *pattern, ...) {
va_list args;
va_start(args, pattern);
int cLen = vsnprintf(NULL, 0, pattern, args) + 1;
va_end(args);
if (cLen < 2) return NULL;
char *fname = malloc(cLen);
if (!fname) return NULL;
va_start(args, pattern);
vsnprintf(fname, cLen, pattern, args);
va_end(args);
return fname;
}
This function creates a formatted string from variable values in a single statement. It uses C's standard formatting syntax as one of its parameters and accepts a variable number of arguments as needed. For example, calling const char *file_name = makeName("%s/S-%.2f_C-%.2e.dat",DIR,S,C); will set file_name to Data/S-0.50_C-1.20e-04.dat if DIR, S and C hold the values Data, 0.5 and 0.00012, respectively.
char **getFList(char *pat, int *count) {
glob_t gBuf;
int gR = glob(pat, 0, NULL, &gBuf);
if (gR != 0) {
if (count) *count = 0;
return NULL;
}
if (count) *count = gBuf.gl_pathc;
return gBuf.gl_pathv;
}
This function returns a list of file paths matching a given pattern, along with the number of matches. It is useful when reading data where the number of sample files varies with the parameters. Calling getFList("data/S-0.50_N-???.dat", &n) will return an array of n file paths matching the pattern, such as data/S-0.50_N-000.dat, data/S-0.50_N-009.dat, and so on. This function is intended for cases where only a few calls are necessary, as the list is allocated internally by glob and is not freed. The memory is released when the program exits. For a large number of calls, memory would accumulate, and a globfree call should be implemented.
double **read_Data(const char *FNL, int *nRows, int *nCols) {
FILE *fp = fopen(FNL, "r");
if (!fp) {
fprintf(stderr, "readData: cannot open file '%s'\n", FNL);
*nRows = 0; *nCols = 0;
return NULL;
}
*nRows = 0;
*nCols = 0;
// Pass 1: read all numeric rows into a temporary flat buffer
double *flat = NULL;
int rows = 0, cols = 0;
int capacity = 1024;
flat = malloc(capacity * sizeof(double));
if (!flat) { fclose(fp); return NULL; }
char line[2048];
while (fgets(line, sizeof(line), fp)) {
// Parse all numbers on this line
double rowBuf[512];
int rowCount = 0;
char *p = line;
while (*p) {
// Skip whitespace
while (*p == ' ' || *p == '\t' || *p == '\r') p++;
if (*p == '\0' || *p == '\n') break;
char *end;
double val = strtod(p, &end);
// If no number could be parsed, the row contains a non-numeric value.
if (end == p) {
rowCount = 0; // mark this row as invalid
break;
}
if (rowCount < 512) rowBuf[rowCount++] = val;
p = end;
}
// Skip empty lines or lines with non-numeric content
if (rowCount == 0) continue;
// First valid row sets the column count
if (cols == 0) {
cols = rowCount;
} else if (rowCount != cols) {
fprintf(stderr, "read_Data, file %s: row %d has %d columns, expected %d\n", FNL, rows + 1, rowCount, cols);
free(flat);
fclose(fp);
*nRows = 0; *nCols = 0;
return NULL;
}
// Grow flat buffer if needed
if ((rows + 1) * cols > capacity) {
capacity *= 2;
double *tmp = realloc(flat, capacity * sizeof(double));
if (!tmp) { free(flat); fclose(fp); return NULL; }
flat = tmp;
}
// Append row to flat buffer
for (int i = 0; i < cols; i++) {
flat[rows * cols + i] = rowBuf[i];
}
rows++;
}
fclose(fp);
// Pass 2: allocate the 2D array and copy data
double **data = malloc(rows * sizeof(double *));
if (!data) { free(flat); return NULL; }
for (int i = 0; i < rows; i++) {
data[i] = malloc(cols * sizeof(double));
if (!data[i]) {
for (int j = 0; j < i; j++) free(data[j]);
free(data);
free(flat);
return NULL;
}
for (int j = 0; j < cols; j++) {
data[i][j] = flat[i * cols + j];
}
}
free(flat);
*nRows = rows;
*nCols = cols;
return data;
}
This function reads numeric data from a text file into a 2D array of doubles, along with the number of rows and columns. Lines that contain any non-numeric value are skipped entirely. All remaining rows must have the same number of columns; if not, the function reports the offending row and returns NULL. The memory for the returned array is allocated on the heap and should be freed by the caller. Call the function as double **FData = read_Data(FN, &rows, &cols); where rows and cols are integer variables, and FN is the file name, which can be generated using the make_Name function.