As input to a linker, Mach-O and ELF files look pretty similar: they both split their contents into named "sections", and then there's a symbol table, which is basically a list of (name, address) pairs.

In C code, each symbol generally represents (the start of) a separate function or variable. All references between them are explicitly marked as relocations; thus, the linker should be free to reorder them, and any function/variable that isn't explicitly referenced is unused and can be removed (unless you're linking a shared library and it's meant to be publicly exported from that).

On the other hand, in assembly code, symbols are not necessarily independent. You can have things like a function that "falls through" into into another function. For example, here's a hypothetical assembly file that implements both bzero() and memset(), and has the former fall through into the latter:

// void bzero(void *s, size_t n)
// (zeroes memory)
_bzero:
// Set up arguments for memset
mov r2, r1 // 3rd argument to memset = 2nd argument to bzero
mov r1, #0 // 2nd argument to memset is 0
// (1st argument to memset = 1st argument to bzero, no move needed)
// Fall through into memset
// void *memset(void *b, int c, size_t len);
_memset:
...memset implementation...

As for sections: traditionally, all code gets put into the same section (named ".text" for ELF, "__text" for Mach-O); all data gets put into the same section (".data" / "__data"); etc.

The Darwin (Mach-O) linker is optimized for the properties of C code, and implicitly treats the data between each symbol and the symbol following it as a separate unit ("atom" or "subsection"). Or, more specifically, it does this if the SUBSECTIONS_VIA_SYMBOLS flag is set in the Mach-O header, which is always the case for object files compiled from C (as of 2005 or so). Thus, it always has the ability to remove unused functions/variables, though it doesn't actually bother to do so unless you pass -dead_strip.

ELF linkers are more traditional and treat each section as an indivisible unit; symbols aren't taken into consideration at all. So, by default, it's not possible to strip unused functions and variables, nor to reorder multiple symbols that came from a single object file. However, you can pass "-ffunction-sections -fdata-sections" to GCC (the compiler, not the linker) to make it put every single function and variable, respectively, in its own section in the .o file. For example, a function named "foo" would appear in a section called ".text.foo". Then the linker will coalesce all the ".text.*" sections back into a single ".text" output, and similarly for other types of sections. But first it can strip unused sections (if you pass --gc-sections) – which is equivalent to stripping unused functions/variables, since each section contains only a single function/variable.

These are basically two different ways to accomplish the same thing, which probably explains what userbinator said. Both approaches feel kind of hacky to me. On the ELF side, object files with a bazillion sections are annoying to look at (if you examine them with readelf or other tools), and putting everything in its own section is not really how sections were originally intended to work. On the Mach-O side, well, the symbol table wasn't originally meant to to be used to split up the input data; in particular, unlike with ELF, Mach-O symbols don't have a size field (which is why the atom implicitly lasts until the next symbol). And it feels wrong that object files compiled from assembly have to be treated differently from everything else (they don't have SUBSECTIONS_VIA_SYMBOLS, unless you explicitly ask for it in the assembly file).

Personally I prefer the Mach-O approach just because it requires fewer flags to enable stripping of unused functions/data. Heck, I don't understand why -dead_strip isn't enabled by default.

But if you were to design a new object file format from scratch, it could probably handle this much more elegantly than either ELF or Mach-O.