library(3avr)

πŸ’– library β€” How to Build a Library πŸ’–

okay bestie, so this is a page from the AVR-LibC manual, and it's literally about how to build your own library. yes, like your own. you're gonna be famous. let's go. 🫢


✨ NAME ✨

library β€” How to Build a Library


🌸 Introduction 🌸

so. you keep rewriting the same functions over and over again?? you're tired of copy-pasting from one project to the next?? you want to reduce your maintenance overhead?? bestie that's the signal, you're ready to create your own library. code reuse is a very laudable goal, no cap. with some upfront investment you can save time and energy on future projects by having ready-to-go libraries. this chapter is gonna walk you through some background info, design considerations, and practical knowledge you need to create and use your own libraries. slay. πŸ’…


πŸ”— How the Linker Works πŸ”—

the compiler compiles a single high-level language file (C, for example) into a single object module file. meanwhile the linker (ld) can only work with object modules to link them together. object modules are the smallest unit the linker works with. periodt.

so typically, on the linker command line, you'll specify a set of object modules (previously compiled, obvi) and then a list of libraries, including the Standard C Library. the linker takes that set of object modules you specified on the command line and links them together. and afterwards there will probably be a set of "undefined references".

a reference is basically a function call. an undefined reference is a function call that has no defined function matching the call. 😭

then the linker goes through the libraries, in order, to match the undefined references with function definitions found in the libraries. if it finds the function that matches the call, the linker links in the object module where that function lives.

and this part is IMPORTANT, bestie, say it with me: the linker links in THE ENTIRE OBJECT MODULE in which the function is located. remember, the linker knows nothing about the functions internal to an object module, other than symbol names (like function names). the smallest unit the linker works with is object modules. that's the vibe.

when there are no more undefined references, the linker has linked everything, is done, and outputs the final application. ✨ iconic behavior ✨


πŸŽ€ How to Design a Library πŸŽ€

how the linker behaves is super important when designing a library. ideally you want to design a library where only the functions that are actually called are the only functions linked into the final application. this helps keep the code size to a minimum. to do that, given how the linker works, you only write one function per code module. that will compile to one function per object module. ngl, this is usually a very different way of doing things than writing an application!

but there are always exceptions to the rule. there are generally two cases where you'd want more than one function per object module. πŸ‘‡

  1. complementary functions that it makes no sense to split up. for example, malloc() and free(). if someone is going to use malloc(), they will very likely be using free() (or at least should be using free() πŸ™ˆ). in this case it makes more sense to aggregate those two functions in the same object module.
  2. when you want to have an Interrupt Service Routine (ISR) in your library that you want to link in. the problem here: the linker looks for unresolved references and tries to resolve them with code in libraries, and a reference is the same as a function call. but with ISRs there is no function call to initiate the ISR. the ISR is placed in the Interrupt Vector Table (IVT), hence no call, no reference, and no linking in of the ISR. πŸ’€ so to do this you have to trick the linker, kind of. aggregate the ISR with another function in the same object module, but have that other function be something the user is required to call in order to use the ISR β€” like maybe an initialization function for the subsystem, or maybe a function that enables the ISR in the first place. sneaky. love that for us. 🧚

πŸ› οΈ Creating a Library πŸ› οΈ

the librarian program is called ar (short for "archiver") and it lives in the GNU Binutils project. this program will have been built for the AVR target, so it will therefore be named avr-ar.

the job of the librarian program is simple: aggregate a list of object modules into a single library (archive) and create an index for the linker to use. the name you create for the library filename must follow a specific pattern: libname.a.

the name part is the unique part of the filename that you create. it makes it easier if the name part relates to what the library is about. this name part must be prefixed by lib, and it must have a file extension of .a, for "archive". the reason for this special form of the filename is how the library gets used by the toolchain β€” as we'll see later on. πŸ‘‘

Note πŸ“ the filename is case-sensitive. use a lowercase lib prefix, and a lowercase .a as the file extension.

the command line is fairly simple:

avr-ar rcs <library name> <list of object modules>

the r command switch tells the program to insert the object modules into the archive with replacement. the c command line switch tells the program to create the archive. and the s command line switch tells the program to write an object-file index into the archive, or update an existing one. that last switch is very important because it helps the linker find what it needs to do its job. 🫢

Note πŸ“ the command line switches are case sensitive! there are uppercase switches that have completely different actions. πŸ’€ MFile and the WinAVR distribution contain a Makefile Template that includes the necessary command lines to build a library. you will have to manually modify the template to switch it over to build a library instead of an application.

see the GNU Binutils manual for more information on the ar program. πŸ“š


πŸ’« Using a Library πŸ’«

to use a library, use the -l switch on your linker command line. the string immediately following the -l is the unique part of the library filename that the linker will link in.

for example, if you use:

-lm

this will expand to the library filename:

libm.a

which happens to be the math library included in AVR-LibC. ⭐

and if you use this on your linker command line:

-lprintf_flt

then the linker will look for a library called:

libprintf_flt.a

this is why naming your library is so important when you create it! 🎯

the linker will search libraries in the order that they appear on the command line. whichever function is found first that matches the undefined reference, it will be linked in.

there are also command line switches that tell GCC which directory to look in (-L) for the libraries that are specified to be linke in with -l.

(yes bestie, the original really does say "linke in" β€” we keep it exactly as-is, no cap, character for character 😭)

see the GNU Binutils manual for more information on the GNU linker (ld) program. πŸ“–


🌈 SEE ALSO 🌈

  • the GNU Binutils manual β€” for more on the ar program (see "Creating a Library")
  • the GNU Binutils manual β€” for more on the GNU linker (ld) program (see "Using a Library")

aww okay that's the whole page bestie, you ate that. go build something. πŸ’–

<3 this was AVR-LibC, Version 2.2.1, and the page itself is library(3avr). mwah, bye. πŸ’…βœ¨πŸŽ€πŸ¦‹

πŸ“¦ where this page comes from