Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Coding Style -- Pass by Reference or Pass by Value?

In order to simplify the development of future school assignments I decided to create an API (is that what you would call it?) for two data structures I commonly use -- a linked list and a hash table.

In developing each of these I ended up with the following two insert functions:

int list_insert(list *l, char *data, unsigned int idx);
int hash_insert(hash_table **ht, char *data);

The list_insert() function (and all of the list functions) ended up being pass-by-value since I never had any need to directly modify the list * itself unless I was malloc'ing or free'ing it. However, because I wanted to include auto-rehashing in my hash table I found that I had to pass the table by-reference instead of by-value in any function that might force a rehash. Now I end up with syntax like the following:

list_insert(l, "foo", 3);
hash_insert(&ht, "foo");

The difference strikes me as a little odd and I found myself wondering if I should change the list functions to be pass-by-reference as well for consistency's sake -- even though none of my functions would need to leverage it. What's the typical consensus here? Should I only pass-by-reference if my function actually needs to modify its arguments or should I pass-by-reference for the sake of consistency?

Structure definitions:

typedef struct list_node list_node;
struct list_node {
    char *data;
    list_node *next;
    list_node *prev;
};

typedef struct list list;
struct list {
    list_node *head;
    list_node *tail;
    size_t size;
};

typedef struct hash_table hash_table;
struct hash_table {
    list **table;
    size_t entries;
    size_t buckets;
    float maxLoad;
    unsigned int (*hash)(char*, unsigned int);
};

List functions:

list *list_createList();
list_node *list_createNode();
void list_destroyList(list *l);
void list_destroyNode(list_node *n);
int list_append(list *l, char *data);
int list_insert(list *l, char *data, unsigned int idx);
int list_remove(list *l, char *data, int (*compar)(const void*, const void*));
void list_push(list *l, char *data);
char *list_pop(list *l);
int list_count(list *l, char *data, int (*compar)(const void*, const void*));
int list_reverse(list *l);
int list_sort(list *l, int (*compar)(const void*, const void*));
int list_print(list *l, void (*print)(char *data));

Hash functions:

hash_table *hash_createTable(size_t buckets, float maxLoad, unsigned int (*hash)(char*, unsigned int));
void hash_destroyTable(hash_table *ht);
list *hash_list(const hash_table **ht);
int hash_checkLoad(hash_table **ht);
int hash_rehash(hash_table **ht);
int hash_insert(hash_table **ht, char *data);
void hash_stats(hash_table *ht);
int hash_print(hash_table *ht, void (*print)(char*));
like image 565
user2506293 Avatar asked Dec 05 '25 08:12

user2506293


2 Answers

Here is a general rule of thumb:

  • pass by value if its typdef is a native type (char, short, int, long, long long, double or float)
  • pass by reference if it is a union, struct or array

Additional considerations for passing by reference:

  • use const if it will not be modified
  • use restrict if pointers will not point to the same address

Sometimes a struct/union seems like the appropriate type, but can be replaced with arrays if the types are similar. This can help with optimization (loop vectorization for example)

like image 96
technosaurus Avatar answered Dec 09 '25 20:12

technosaurus


That's up to you and takes a little intuition. When passing large structs I pass by reference so that I am not eating up extra stack space and burning cycles copying the struct. But with small struts like yours it may be more efficient to use the stack depending on your target processor, how often you are using the values, and what your compiler does. Your compiler may break that struct up and put its values into registers.

But if you do pass by reference and do not intend to modify the value it is best practice to pass a pointer to const, eg: const list * l. That way there isn't any risk of you accidentally modifying the value and it makes the interface cleaner- now the caller knows that the value won't be changing.

Consistency is nice and I personally would lean in that direction especially on large interface because it may make things easier in the long run, but I would definitely use const. In doing so you allow the compiler to discover any accidental assignments so that later you don't need to track down a hard to bug.

See also: Passing a struct to a function in C

like image 31
Nick Avatar answered Dec 09 '25 19:12

Nick



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!