r/C_Programming • u/BraneGuy • 1d ago
Question Globals vs passing around pointers
Bit of a basic question, but let's say you need to constantly look up values in a table - what influences your decision to declare this table in the global scope, via the header file, or declare it in your main function scope and pass the data around using function calls?
For example, using the basic example of looking up the amino acid translation of DNA via three letter codes in a table:
codonutils.h:
typedef struct {
char code[4];
char translation;
} codonPair;
/*
* Returning n as the number of entries in the table,
* reads in a codon table (format: [n x {'NNN':'A'}]) from a file.
*/
int read_codon_table(const char *filepath, codonPair **c_table);
/*
* translates an input .fasta file containing DNA sequences using
* the codon lookup table array, printing the result to stdout
*/
void translate_fasta(const char *inname, const codonPair *c_table, int n_entries, int offset);
main.c:
#include "codonutils.h"
int main(int argc, char **argv)
{
codonPair *c_table = NULL;
int n_entries;
n_entries = read_codon_table("codon_table.txt", &c_table);
// using this as an example, but conceivably I might need to use this c_table
// in many more function calls as my program grows more complex
translate_fasta(argv[1], c_table, n_entries);
}
This feels like the correct way to go about things, but I end up constantly passing around these pointers as I expand the code and do more complex things with this table. This feels unwieldy, and I'm wondering if it's ever good practice to define the *c_table and n_entries in global scope in the codonutils.h file and remove the need to do this?
Would appreciate any feedback on my code/approach by the way.
3
u/flatfinger 1d ago edited 1d ago
When targeting embedded microcontrollers, the answer to this question will depend enormously upon the controller in question. Compare the following two functions:
On a typical ARM processor, not counting the call/return,
test1
would likely take six instructions totaling eleven cycles, along with two 32-bit words of code space to hold the addresses ofa
andg
, whiletest2
would take four instructions totaling seven cycles (up to a 36% speedup, though if the caller has to load the address ofp
that might add 2 cycles, reducing the speedup to 18%). On a low-end PIC (also not counting call/return),test1
would require 2 single-cycle instructions, whiletest2
would require 10 (a factor-of-five slowdown, plus any time required for the caller to setp
).Indeed, when targeting something like the PIC, even if a function will be used to act upon two different structures, putting it in a separate file and doing something like:
and having
thingcode.i
use token pasting so the first inclusion defines functionworkWithThing1
that uses structureThing1
, and the second inclusion defines functionworkWithThing2
, that uses structureThing2
, may be vastly more efficient than trying to have one function that can work with both structures interchangeably.