Ερώτηση για χρησιμότητα πολλών πηγαίων αρχείων σε γλώσσα C

Καλημέρα σε όλους.
Βρίσκομαι στη αρχή της συγγραφής νέου κώδικα σε γλώσσα C για προγραμματισμό μικροελεγκτή.
Μέχρι τώρα έκανα μερικές εφαρμογές με ένα και μοναδικό πηγαίο αρχείο. Τώρα χρησιμοποιώ πρώτη μου φορά και αρχεία κεφαλίδας “.h”, στα οποία θα βάλω όλες τις δηλώσεις μεταβλητών και συναρτήσεων. Θα νοικοκυρευτώ δηλαδή. Μου άρεσε πολύ η χρήση τους.
Εκείνο που δεν καταλαβαίνω είναι η δυνατότητα πολλών πηγαίων αρχείων. Ποια είναι η χρήση τους; Βλέπω πως η “main” καλεί συναρτήσεις και περιεχόμενο από τα αρχεία κεφαλίδας. Ποια η διαφορά λοιπόν του (δεύτερου) πηγαίου αρχείου από το αρχείο κεφαλίδας;

Καλώς ήρθες στον μαγικό κόσμο των μικροελεγκτων.

Όταν φτιάχτηκε η γλώσσα C ο υπολογιστής που χρησιμοποιήθηκε είχε μικρότερη μνήμη από τον μικροελεγχτή σου! Για τον λόγο αυτό θα έπρεπε να είναι απλή.

Ένας τρόπος ήταν να μην ψάχνετε για το τι είναι το κάθε σύμβολο, αλλά να είναι δηλωμένο (declaration) πριν όποια αναφορά σε αυτό. Δηλαδή να ξέρουμε τον τύπο του (και από αυτό το πόσα bytes θέλει ίσως το πιο σημαντικό) το όνομα του, παραμέτρους αν είναι συνάρτηση(άρα και πόσο χώρο θέλουν). Με αυτές τις πληροφορίες μπορεί να γίνει η δουλεία ακόμα και αν δεν ξέρουμε τον ορισμό (definition) του συμβόλου. Στο τελευταίο βήμα μιας μεταγλώττισης ο linker θα αναζητήσει τους ορισμούς (definitions) και τον κώδικα ή την θέση μνήμης μιας μεταβλητής, αν τους γίνετε χρήση.

Δεν ξέρω αν τα παραπάνω βγάζουν νόημα, ίσως να πρέπει να τα διαβάσεις 2-3 φορές :joy:

Για παράδειγμα έστω ότι η C βλέπει μια γραμμή: a=bufone(42); Θα πρέπει να ξέρει αν το bufone είναι συνάρτηση και τι τύπο παραμέτρων παίρνει. Πιθανά να θέλει float και να πρέπει να γίνει μια μετατροπή τύπου. Το ίδια και για την έξοδο. Αν τα ξέρει αυτά μπορεί να το μεταφράσει σε κώδικα μηχανής χωρίς να ξέρει τι κάνει ή bufone ούτε να γνωρίζει τον κώδικα της. Γκέγκε;

Εδώ λοιπόν είναι η χρήση των αρχείων κεφαλίδας. Παρέχουν τα declarations. Και επειδή έπρεπε να είναι απλή λόγω μνήμης, κάνει το πιο απλό πράγμα που μπορούσε να κάνει, δηλαδή ένα copy/paste.

Αν λοιπόν ο κώδικας γίνει μεγάλος βολεύει να είναι σε πολλά μικρά αρχεία. Εκεί θα είναι τα διάφορα definitions, δηλαδή ο κώδικας. Και τα header files θα περιέχουν τα declarations για να μην τα γράφουμε σε κάθε αρχείο κώδικα χωριστά αλλά και για να κρατάμε τις δηλώσεις συγχρονισμένες. Όσο για τα πολλαπλά αρχεία τα χρησιμοποιείς ήδη, για κάθε τι υπάρχει το definition του σε κάποιο αρχείο.

Για παράδειγμα όταν γράφεις digitalWrite(LED_PIN, LOW); θα πρέπει να ξέρει τι είναι τα 3 σύμβολα που χρησιμοποιούνται (δηλαδή τις δηλώσεις). Η δε digitalWrite() υπάρχει ορισμένη σε κάποιο αρχείο. Στο Arduino η δήλωση της βρεθεί στο αρχείο hardware/arduino/avr/cores/arduino/wiring_digital.c και ο ορισμός της στο αρχείο Arduino.h.

Στους μικροελεγχτες συνήθως κάνουμε τα πάντα compile κάθε φορά αντί να χρησιμοποιούμε προτηγανισμένες βιβλιοθήκες. Στον υπολογιστή θα ήταν βασανιστικό να κάνεις compile όλες τις βιβλιοθήκες που χρησιμοποιεί ένα προγραμμα. Θα ήθελε ώρες.

Σημείωση: Και μετά έχουμε το Arduino IDE. Στην προσπάθεια του να γίνει προσιτό αποκρύβει αρκετά από τα παραπάνω και πριν μεταγλωττίσει τον κώδικα σου (που είναι μια “κουτσουρεμένη” C++ στην πραγματικότητα) του κοτσάρει ένα #include <Arduino.h> στην αρχή καθώς και επίσης φτιάχνει και προσθέτει (με απλοϊκό τρόπο) τα declaration των συναρτήσεων σου. Ίσως από εκεί να ξεκινάει η σύγχυση :crying_cat_face:

Ελπίζω να βοήθησα :sunglasses:

1 «Μου αρέσει»

Μπράβο, κατανοητά όλα. Είμαι με PIC μικροελεγκτή.
Και κάνω το ερώτημά μου πιο συγκεκριμένο.
Από τη στιγμή που μπορώ σε ένα ή πολλά αρχεία κεφαλίδας (.h) να έχω και τους ορισμούς ΚΑΙ τις δηλώσεις, γιατί να χρησιμοποιήσω πολλαπλά πηγαία αρχεία και όχι πολλαπλά αρχεία κεφαλίδας;
Διαφέρει κάπου ο χειρισμός τους ή είναι καθαρά ζήτημα οργάνωσης;

Όχι, κατηγορηματικά όχι. Δεν θέλω να μπω στα βαθιά και να πετάω κουβέντες για “one definition rule” ή για “include guards”. Τον κώδικα σου θα τον βάζεις πάντα σε αρχεία με κατάληξη ".c" (προσοχή όχι σε κάτι άλλο, αν τελειώνει σε ".cpp" θα είναι C++ και κάποια πράγματα είναι πολύ πιο αυστηρά).

Τα header files είναι “προαιρετικά”, αλλά εσύ θα έχεις πάντα τουλάχιστον ένα, όπου θα κρατάς συγκεντρωμένες τις δηλώσεις των συναρτήσεων.

Μπορεί να σου δουλεύει τώρα στο απλό που φτιάχνεις, ο προεπεξεργαστής απλά κάνει ένα copy/paste, αλλά ο μηχανισμός είναι διαφορετικός και σε κάτι πολυπλοκότερο θα σου βγάλει θεματάκια. Τα header files ήταν ένα έξυπνο hack για τους υπολογιστές του 70. Σήμερα γλώσσες όπως η go δεν τα χρησιμοποιούν και η C++20 επιτέλους παρέχει μηχανισμούς για να τα ξεφορτωθούμε κάποια στιγμή.

ΥΓ: Πως κατέληξες στον PIC και όχι σε κάποιο Arduino Uno; Έχω ανακαλύψει τον RP2040 (Raspberry pico) εδώ και λίγο καιρό, και έχω πάθει πλάκα με την τιμή τις δυνατότητες και το VFM.

1 «Μου αρέσει»

Κατάλαβα, θα ακολουθήσω αυτή την πρακτική.
Έτυχε και ξεκίνησα με μικροελεγκτές PIC πριν από χρόνια, αργότερα τα παράτησα λόγω έλλειψης χρόνου.
Τώρα μερακλώθηκα πάλι και επειδή έχω ήδη κάποιες γνώσεις, είπα να μην αλλάξω οικογένεια και αρχίζω πάλι το διάβασμα από το μηδέν.

Το αφήνω εδώ γιατί μπορεί να φανεί χρήσιμο σε κάποιον μελλοντικά.
Realtime PIC/Arduino emulator για πειραματισμούς :

1 «Μου αρέσει»