The huge and important difference is when you use this pattern in C the compiler has no idea what you are doing and can't check anything, nor can it reduce the cost of this dispatch mechanism. A compiler for a language where this is a feature can check what you are doing and de-virtualize virtual dispatch. In C you pay the highest possible cost for polymorphism while enjoying the least benefits.
> The huge and important difference is when you use this pattern in C the compiler has no idea what you are doing and can't check anything
Function pointers exist in C and are type-checked by the compiler just fine.
> A compiler for a language where this is a feature can check what you are doing and de-virtualize virtual dispatch.
Virtual dispatch is pretty much only used in the kernel in places where virtualization is necessary. Devirtualization is pretty rare in C++ in practice (I don't believe I've ever seen a "virtual final" method, and my day job is on a large C/C++ codebase).
> Function pointers exist in C and are type-checked by the compiler just fine.
This amount of type safety is almost useless. Many of the function prototypes, for example in inode and dentry ops, have the exact same signature. If you accidentally swap inode.rmdir with inode.unlink, compiler isn't going to say anything. And it won't be caught in code review either, to the extent that Linux even has code review culture.
> I don't believe I've ever seen a "virtual final" method, and my day job is on a large C/C++ codebase
It's common in my experience, for performance-sensitive code.
I don't think that kind of optim is usually important in a kernel, especially one architected a lot with dynamical modules. Plus, on the convenience side, you can also use other models (e.g. have some function pointers at instance level) than the one of C++ in ways that do not look like completely different when reading the source.
Devirtualization is mainly useful to cope with some C++ self inflicted wounds. Linux obviously never had them the first place.
I'm really not a compiler expert, but is this optimization common in C++ for example? I thought all virtual methods incurred the cost of a vtable lookup
Just slapping the virtual keyword on some function doesn't do anything. Overrides may or may not happen via vtable. Often compilers can figure out how to dispatch at compile time and don't need the vtable.