Yes your code has undefined behaviour.
v[0] calls a function which returns int&, and there are no "null references", so &v[0] can never be NULL in the first place. This is why clang with optimisations makes your code print 0, and optimises the following to return false;:
bool is_pointer_to_first_null(std::vector<int>& v) {
return &v[0] == nullptr;
}
(Which clang also warns you of)
It also breaks the contract for std::vector<T>::operator[](n), which is defined as equivalent to *(v.begin() + n), and since v.begin() + 0 == v.begin() == v.end() for an empty vector, this is not a dereferenceable iterator, and your standard library implementation is allowed to do anything.
For example, with _GLIBCXX_DEBUG defined, this prints Error: attempt to subscript container with out-of-bounds index 0, but container only holds 0 elements. with g++. (https://godbolt.org/z/anran5)
The fact that &v[0] == nullptr isn't optimised to false in gcc is a missed optimisation. Perhaps &v[0] is already optimised to v.data() first and the information that this is non-null is lost (or not used for whatever reason).
And without optimisations, gcc will gladly form null-references since references are implemented as pointers and &reference is a noop in assembly. This is true for most compilers on most architectures, so this is unlikely to segfault.
Perhaps you are looking for v.empty() (which could still have a non-null data pointer), v.data() == nullptr (When defined, v.data() == &v[0]), or v.capacity() == 0? There shouldn't be a good reason to check if the data pointer is null though.