Coding style - go by link or go by value?

To make it easier to develop future school assignments, I decided to create an API (is that what you would call it?) For the two data structures I usually use - linked list and hash table.

While designing each one I got the following two insert functions:

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

      

The function list_insert()

(and all the list functions) ended up being passed by value as I never had to directly modify it myself list *

unless I was malloc'ing or free'ing. However, since I wanted to enable auto-replay in my hash table, I found that I had to pass the table-reference instead of the value-value in any function that might force the rename. Now I end up with the syntax like this:

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

      

The difference seems a bit odd to me, and I wondered if I needed to change the list functions to be pass by reference, and also for the sake of consistency - although none of my functions would need to use it. What is the typical consensus here? Should I only pass by reference if my function really needs to change its arguments, or should I pass by reference for 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*));

      

+3


source to share


2 answers


Here's a general rule of thumb:

  • pass a 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, structure or array

Additional considerations for transfer by reference:



  • use const if it will not be modified
  • use constraint if pointers won't point to the same address

Sometimes struct / union seems like a suitable type, but can be replaced with arrays if the types are similar. This can help with optimizations (e.g. loop vectorization)

+1


source


It's a little intuitive for you. When passing large structures, I pass the reference so that I don't eat up extra stack space and write cycles while copying the structure. But with small racks like yours, it might be more efficient to use the stack depending on your target processor, how often you use values, and what your compiler is doing. Your compiler can break this structure and put its values ​​in registers.

But if you're passing by reference and does not intend to change the value, it is best to pass a pointer to const, for example const list * l

. This way, you don't run the risk of accidentally changing the value and making the interface cleaner β€” now the caller knows the value won't change.



Consistency is good and I personally lean in this direction, especially on a large interface, because it can make things easier in the long run, but I will definitely use const. In doing so, you allow the compiler to detect any random jobs so you don't have to track down a complex error later.

See also: Passing a structure to a function in C

+1


source







All Articles