Linking is a skill that's worth picking up if you ever intend to do system level things. I ended up learning the basics in a few different projects: back in the days of 8 bit systems using DOS and real mode 8086 applications, segment overlays were a common way of dealing with the lack of RAM for holding binaries and data. As a result, you had to learn how to tell the linker which pieces of code would overlay others and what was shared. One of the projects written in QuickBASIC that I was exposed to had to do this because of the amount of business logic that existed in that space.

As a curious systems minded person, at one point I wrote my own port of the Amiga exec.library and a few device drivers that ran in x86 real mode in Microsoft C with a bit of inline assembly. It was necessary to do some linker magic to get things in the right place to be loaded from a custom boot sector. The whole thing did all the task switching and message passing that was done on the Amiga, and was a great learning experience. I spent many, many hours disassembling the Amiga's ROM and other libraries to follow the code paths and reverse engineer exactly how the system worked. There were lots of cool details in the internals that dynamically discovered drivers and libraries at run time inside the ROM. The Amiga was a great learning platform with preemptive multitasking, Autoconfig and so many other nifty features.

Learning GNU binutils came about in part from working on the Linux kernel, as well as building a system from scratch during the a.out to ELF transition in the mid 1990s. The info pages are very thorough. That experience was invaluable, and combined with everything else was a great way of gaining the skill set needed to work on embedded systems in my later career.

IDEs are a great tool, but nothing beats the experience of putting the bits in the right spot yourself. The university compilers and OS courses will cover a lot of concepts, but not necessarily all of the open source tools available. If that isn't something you've gone through, I'd recommend looking at Linux From Scratch as a good starting place if that piques your interest. Knowing how to use command line tools like awk and sed, editors like vim and emacs, bash, tcsh, ssh, make.... They all take time to learn how to use, but they're powerful tools that let you do things that would take forever using a GUI (I find controlling a computer with the keyboard is faster than taking my hand off the keyboard to move the mouse).

There's no better way to solidify learning about programming than to try to bulid something from nothing, like writing a compiler for a language with a hand written recursive descent parser or implementing a routing protocol based on an IETF RFC. Every project contributes to a body of knowledge about what approaches to writing software work well and what becomes a huge time sink. You have to have experience to intuit what data structures fit when solving a particular problem.

For me many of these experiments were done in C because that's what existed in the Linux open source ecosystem. With the power of computers today compared to 30 years ago, languages like python are Good Enough for many problems, but python is never going to expose you to what really goes on under the hood. It was somewhat easier growing up in the 1980s as we were forced to use a whole bunch of different platforms from the 8 bit 6502 using in the Apple ][, Vic 20 and Commodore 64 to the 16/32 bit 68000 in the Amiga and then on to the 32 bit i386/i486, and there were great magazines like Byte that had loads of in depth technical content on these platforms as they were introduced every month. There's a lot more information available at our fingertips today via the internet, but the curated magazines of the 1980s and early 1990s exposed one to many things that you wouldn't necessarily think of looking for.

Ah.... I'm getting old and raveling in the nostalgia of youth!