On "NULL Pointer". That entire concept is abstract and a made up convention.
It's just the "invalid value sentinel" concept, very usable all across programming, with some pros and cons. Yes, arbitrary, made up convention, like most things are.
The alternative is storing the validness separately. Magic values (sentinels) have performance benefits: saves the memory of the separate "validness" marker, and saves the few instructions to load that marker from memory into CPU registers. One load for the value (in this case, pointer) itself, then check for validness, then keep using the value - smaller code than with separate validness variable.
The shortcomings are, you lose the possibility to represent that sentinel in the data, and you need to be careful that every participant knows exactly what the sentinel is.
struct
{
int16_t temp_celsius;
int16_t is_valid;
} temperature;
vs.
#define TEMPERATURE_INVALID INT16_MAX
int16_t temperature_celsius = TEMPERATURE_INVALID;
For the NULL pointers, both shortcomings have sometimes realized:
most systems use 0 as invalidity sentinel, and most people think this is always the case. Leaves rare cases when the actual NULL pointer in-memory isn't 0, and people get surprised. And the other shortcoming: some systems
do place something at memory address 0. For example, STM32H743 places DTCM memory. It's valid start from 0. And there's normally no reset vector or anything like that there; just the user functions right from the start! So a sensible programmer just chooses to waste the first 4 bytes and write their linker script to start at 0x000004, but that happens through trial and error once you find out that GCC fails your init script when it starts to copy from address 0. ST could have chosen any other address, like on most of their other product lines, say, maybe 0x10000000 or anything. No idea why they did this. Maybe just to confuse people, like they do with their IO and DMA mappings.