Internally, SEL is equivalent to const char[] that simply holds method name. In fact, they are just C strings:
(lldb) p _cmd
(SEL) $0 = "windowDidLoad"
(lldb) p (const char*) _cmd
(const char *) $1 = 0x91ae954e "windowDidLoad"
The important exception is that these pointers are globally unique in the process, even across static modules and dynamic library boundaries, so they are comparable using ==. Unlike C strings which can not be compared by pointer values ("instanceMethod" == @selector(instanceMethod) may and will fail), selectors are comparable by pointer value: no matter how selector was created, two SEL values for the same selector are always equal.
@selector(instanceMethod) syntax creates C string "instanceMethod" then passes to Obj-C runtime function which turns it into unique pointer value corresponding to that string. What it basically does is
SEL sel = @selector(instanceMethod);
SEL sel = sel_registerName("instanceMethod");
P.S. struct objc_selector does not exist, it is to make SEL values incompatible with C strings, and hide selector implementation details from you. For even better understanding, you may read in Obj-C runtime source code for selectors what those sel_getName and sel_registerName actually do.