Enlightened


  • Discourse touched me in a no-no place

    @djls45 said in Enlightened:

    They are fully interchangeable

    Nope. Alas. If your target is using the medium memory model, code pointers are larger than data pointers. If your target is using the compact memory model, data pointers are larger than code pointers.

    The wreckage of all that shit is still infesting C and C++ to this day.



  • @dkf said in Enlightened:

    @djls45 said in Enlightened:

    They are fully interchangeable

    Nope. Alas. If your target is using the medium memory model, code pointers are larger than data pointers. If your target is using the compact memory model, data pointers are larger than code pointers.

    Ah, I forgot about that difference between medium and compact. But they can still be interchanged if you know how they are formatted differently.

    And I'd argue that this is an issue with the Intel memory system instead of a problem inherent to C/C++.


  • Discourse touched me in a no-no place

    @djls45 There also used to be a problem with char* on Crays, which would be larger than an int* because the smallest addressable unit was 32 bit word, so char* was internally a structure that contained the real pointer and a shift offset within the word that the pointer pointed to. I remember this vividly because a customer complained that my code wouldn't compile on their hardware. 😡

    The More You Know…


  • Java Dev

    @djls45 said in Enlightened:

    In C and C++, a pointer-to-a-method and a pointer-to-an-instance are exactly the same.

    I know the Standard explicitly allows the two to be different. Not sure how that works with pointers to void.

    @djls45 said in Enlightened:

    @asdf said in Enlightened:

    @dkf said in Enlightened:

    And C++ remains the worst of the worst for compilation speed and gnosticity of error messages.

    As one of my co-workers​ likes to say: The moment GCC's error messages start making sense to you, you'll know you've lost your sanity.

    I must be insane, because they (mostly) make sense to me.

    For C or for C++? I've never had much trouble with the C ones.



  • @asdf said in Enlightened:

    As one of my co-workers​ likes to say: The moment GCC's error messages start making sense to you, you'll know you've lost your sanity.

    Oh. Fuck.

    edit: And, yes, I can usually understand the template ones too (where I'm using a template, not writing it)



  • In C and C++, a pointer-to-a-method and a pointer-to-an-instance are exactly the same.

    @djls45 Nope, pointers to methods come with an added offset because they may have to adjust the "this" pointer you give them when calling. Or at least, they can come with one, if the class is detected to be within a class hierarchy, or something: Pointers to member functions are very strange animals

    Then you get pointers to member variables, which are basically offsets into the object...



  • @PleegWat said in Enlightened:

    @djls45 said in Enlightened:

    In C and C++, a pointer-to-a-method and a pointer-to-an-instance are exactly the same.

    I know the Standard explicitly allows the two to be different. Not sure how that works with pointers to void.

    In theory void (*)() is supposed to accept any function pointer in C, but the C standard does not mandate it fitting in void*. In practice, the POSIX standard does (because of dlsym()) and Windows may have similar restrictions, so I know of no platform where these two are different.



  • @Medinoc said in Enlightened:

    In C and C++, a pointer-to-a-method and a pointer-to-an-instance are exactly the same.

    @djls45 Nope, pointers to methods come with an added offset because they may have to adjust the "this" pointer you give them when calling. Or at least, they can come with one, if the class is detected to be within a class hierarchy, or something: Pointers to member functions are very strange animals

    Then you get pointers to member variables, which are basically offsets into the object...

    But those are all still implementation details that vary by compiler. The underlying pointer itself is still the same.
    From your link:

    Warning: The discussion that follows is specific to the way pointers to member functions are implemented by the Microsoft Visual C++ compiler. Other compilers may do things differently.



  • @PleegWat said in Enlightened:

    @djls45 said in Enlightened:

    In C and C++, a pointer-to-a-method and a pointer-to-an-instance are exactly the same.

    I know the Standard explicitly allows the two to be different. Not sure how that works with pointers to void.

    Any pointer can be converted to a void *, and a void * can be converted to any pointer.


  • Java Dev

    @djls45 Yeah, so if pointers to functions are larger than pointers to data, this necessitates that pointers to void are larger than pointers to data as well.



  • @RaceProUK said in Enlightened:

    It's also a massive leaker of memory, even when used properly, since it never actually unallocates memory before nullifying the reference to it.

    It never allocates memory in the first place, so leaking memory actually is one of the problems it doesn't have.



  • @PleegWat said in Enlightened:

    @djls45 Yeah, so if pointers to functions are larger than pointers to data, this necessitates that pointers to void are larger than pointers to data as well.

    Indeed so, but again, that's dependent on the compiler/processor/memory mode. It's not a problem in the language itself.



  • @cvi said in Enlightened:

    @RaceProUK said in Enlightened:

    It's also a massive leaker of memory, even when used properly, since it never actually unallocates memory before nullifying the reference to it.

    It never allocates memory in the first place, so leaking memory actually is one of the problems it doesn't have.

    The problem isn't that it doesn't leak memory itself; it's that it fails at actually being a singleton. The first instance has to be allocated with new, which allocates memory for it. Any subsequent calls to new create a separate instance and points the shared ms_singleton to that new instance. The calling code is expected to clean up (delete) the instance(s), which is contrary to the purpose of a singleton. This also has the effect of destroying any other state that derived classes introduce whenever an instance is desired.



  • @djls45 Yeah, but if you look at the code, you'll see that it fails much harder (and in much more fundamental ways) than not being a singleton. The fact that it isn't a singleton is really one of the least problems that the code has.

    (Hint: try to instantiate the class with something larger than the average padding of your platform and/or anything non-trivial, so a std::string or so. I've seen singletons that failed at being singletons but that didn't blow up the entire process along the way.)



  • @djls45 There's no instance of T anywhere. It basically just takes this, which is singleton<T>, and casts it to T*. I imagine it would blow up nicely if T is int[100].

    Fake edit: what @cvi said.



  • And because I like to see how they might actually be used:

    singleton<int> MySingleton;
    singleton<int> MyOtherSingleton; // MySingleton.instance() now refers to MyOtherSingleton
                                     // but none of the other state is copied. The rest of MySingleton still exists on its own.
    

    Granted, these are scoped variables, but trying to create unscoped instances also fails:

    singleton<int>* MySingleton = new singleton<int>();
    singleton<int>* MyOtherSingleton = new singleton<int>(); // This will either abort the program
                                                             // or happily ignore that there is already an instance and create a new one.
    

    But I bet this is how it's actually used:

    new singleton<int>(); //create new singleton
    /*if(!singleton<int>::instance_ptr())
    {
        singleton<int>::instance_ptr() = new int;
    }*/
    int MySingleton = singleton<int>::instance();
    int MyOtherSingleton = singleton<int>::instance();
    
    // do stuff...
    
    //clean up singleton
    if(singleton<int>::instance_ptr())
    {
        delete singleton<int>::instance_ptr();
    }
    


  • @djls45 said in Enlightened:

    singleton<int>* MySingleton = singleton<int>::instance();

    instance() returns a T& to a non-existent T. So that line won't even compile (because of the type, the UB is just a bonus).



  • @cvi said in Enlightened:

    @djls45 said in Enlightened:

    singleton<int>* MySingleton = singleton<int>::instance();

    instance() returns a T& to a non-existent T. So that line won't even compile (because of the type, the UB is just a bonus).

    Not quite. The last line from the posted code is
    template <typename T> T * singleton <T>::ms_singleton = NULL;
    which means that *MySingleton == NULL. Which is still wrong and will produce a null pointer exception as soon as anything attempts to use it, but it will compile.

    Edit: Oh, derp. I missed the return type of instance(). I'll fix it.



  • @djls45 said in Enlightened:

    null pointer exception

    Null pointer exception?!?

    0_1493403081473_upload-b8cf4a2f-1aef-4221-81b0-a71f9d9e7325



  • @djls45 said in Enlightened:

    new singleton<int>(); //create new singleton

    Yeah, it created a new singleton<int>, but it didn't create an int!



  • @cvi said in Enlightened:

    @djls45 said in Enlightened:

    null pointer exception

    Null pointer exception?!?

    0_1493403081473_upload-b8cf4a2f-1aef-4221-81b0-a71f9d9e7325

    The fact that it happens via application crash to the system doesn't change the fact that it is an exceptional state caused by attempting to dereference a null pointer.

    Ah, that's right, SEGFAULT is the term I meant. (I've been coding in Java too much recently.)



  • @NedFodder said in Enlightened:

    @djls45 said in Enlightened:

    new singleton<int>(); //create new singleton

    Yeah, it created a new singleton<int>, but it didn't create an int!

    Ah, you're right. Fixed.

    It's apparently a really convoluted way to create aliases with a very high potential for buggy code.



  • @djls45 Still wrong:

    new singleton<int>(); //create new singleton
    if(!singleton<int>::instance_ptr())
    {
        singleton<int>::instance_ptr() = new int;
    }
    

    That will never go into the if. Here's the constructor:

    	singleton()
    	{ 
    		assert(!ms_singleton);
    		long offset = (long) (T*) 1 - (long) (singleton <T>*) (T*) 1; 
    		ms_singleton = (T*) ((long) this + offset);
    	} 
    


  • @djls45 Well, technically, dereferencing a null pointer is not required to do anything, it's just undefined behaviour. You may get a segfault, or something else may happen. If your program is simple enough, the compiler may see it, and assume that such can never occur and optimize accordingly. (Clang occasionally replaces obvious null pointer derefs with ud2.)



  • @djls45 said in Enlightened:

    singleton<int>::instance_ptr() = new int;

    Won't compile, can't assign to a l-value.



  • @cvi said in Enlightened:

    or something else may happen

    Formatting your disk is a valid option.



  • @cvi It would have to return T** for that to work, right?



  • @dcon said in Enlightened:

    @cvi said in Enlightened:

    or something else may happen

    Formatting your disk is a valid option.

    I'd hate to be working with that compiler. Might recommend it to my students, though.



  • @NedFodder T*&, otherwise you'd need to dereference the returned pointer (to pointer).


  • Discourse touched me in a no-no place

    @Medinoc said in Enlightened:

    I know of no platform where these two are different.

    These days, you have to be on one of the embedded platforms for those sorts of things to be a major issue.



  • @NedFodder said in Enlightened:

    @djls45 Still wrong:

    new singleton<int>(); //create new singleton
    if(!singleton<int>::instance_ptr())
    {
        singleton<int>::instance_ptr() = new int;
    }
    

    That will never go into the if. Here's the constructor:

    	singleton()
    	{ 
    		assert(!ms_singleton);
    		long offset = (long) (T*) 1 - (long) (singleton <T>*) (T*) 1; 
    		ms_singleton = (T*) ((long) this + offset);
    	} 
    

    @cvi said in Enlightened:

    @djls45 said in Enlightened:

    singleton<int>::instance_ptr() = new int;

    Won't compile, can't assign to a l-value.

    Oh, right. Hmmmm....

    It seems that it's just a dummy wrapper over anything that can fit into a pointer's memory size.

    Fake edit: OH! I just noticed! singleton<T>::ms_singleton is public!
    This class is even more useless than it first appears.



  • @djls45 said in Enlightened:

    a very high potential for buggy code.

    @djls45 said in Enlightened:

    This class is even more useless than it first appears.

    You have achieved Enlightenment.


  • Java Dev

    @NeighborhoodButcher said in Enlightened:

      	long offset = (long) (T*) 1 - (long) (singleton <T>*) (T*) 1; 
    

    Actually, what does it even mean here to cast from T* to singleton <T>* here, as the two don't have an inheritance relation? I'd expect it just ends up being a C-style interpret the same memory a different way.


  • Winner of the 2016 Presidential Election

    @dcon said in Enlightened:

    And, yes, I can usually understand the template ones too (where I'm using a template, not writing it)

    I can usually deduct what exactly caused GCC to produce this message. That's different from seeing the message and thinking "Oh, yeah, that makes total sense!", though. ;)



  • @djls45 said in Enlightened:

    And because I like to see how they might actually be used:

    Btw - I'd guess this was "designed" to be used with the CRTP, so might want to try that. ;-)



  • @asdf said in Enlightened:

    The moment GCC's error messages start making sense to you, you'll know you've lost your sanity.

    Damn. Oh well, not like I was using it.

    @djls45 said in Enlightened:

    In C and C++, a pointer-to-a-method and a pointer-to-an-instance are exactly the same.

    I'm pretty sure that's not right. You can't, for example, cast a pointer-to-function to void* and back safely, which you can with pointers to objects: pointers

    Pointers to void
    Pointer to object of any type can be implicitly converted to pointer to void (optionally cv-qualified). The reverse conversion, which requires static_cast or explicit cast, yields the original pointer value

    Evidence: cppshell
    Try to run it and you will get the error: error: invalid conversion from 'int (*)()' to 'void*' [-fpermissive] . Pointers to member functions are even more weird. It's not just a matter of size or format. You are not allowed to do the same operations on each. I mean, you might as well say that all integer types are interchangeable, because they are ultimately bit patterns that can be interpreted, but then you've obliterated the concept of types altogether.

    @djls45 said in Enlightened:

    Any pointer can be converted to a void *, and a void * can be converted to any pointer.

    As I pointed out above, no you can't. The standard only allows void* to point to objects of indeterminate type.



  • @Kian

    The standard only allows void* to point to objects of indeterminate type.

    "indeterminate" = any
    ^This also implies that pointers are the same size, which (I think?) was the main point of disagreement. (The exception would be working on a system with totally separated memory for .text and .data. 16-bit addressing uses special pointer operations to access 20-bit memory addresses, but the pointers are all still 16-bits wide. That's the weird thing with the medium and compact Intel memory modes, IIRC.)

    If you already know that you have a function pointer, you can pass it around as a void* and convert it back in order to call a method with it. The -fpermissive flag allows you to do things that technically work, even though it might break type-safety. The fact that the compiler can watch for breakage of pointer type-safety doesn't mean that the pointers to different types are fundamentally different things. A pointer variable is simply a place to store a memory address. As such, any pointer can be treated as a pointer to any type, though in most cases that would not be useful. C/C++ allows it though, which makes it much easier to write things like self-modifying code.

    It does obliterate the concept of compiler-enforced types, so it should only be used if someone really knows what they're doing and can't do it another way, but the language does allow it, and it does work.


  • Discourse touched me in a no-no place

    @djls45 said in Enlightened:

    it does work

    On most platforms, but not all. Systems that totally partition instruction memory from data memory exist, even if it isn't a very common configuration.



  • @djls45 said in Enlightened:

    "indeterminate" = any
    ^This implies that pointers are the same size (unless you're using the compact or medium 32-bit Intel memory models or are working on a system with totally separated sections of memory for .text and .data).

    Yes, but functions are not objects. Objects are instances of a type. Void pointers can point to any instance of any type. It can't point to functions, which are not instances of a type. Functions have signatures, and a given function pointer can point to any function with a matching signature, but a signature is not a type.

    @djls45 said in Enlightened:

    The -fpermissive flag allows you to do things that technically work, even though it might break type-safety.

    Breaking type safety means going outside the bounds of the language, essentially saying to the compiler "hold my beer" while you do something that goes against the rules of the language. You are relying on implementation details that you know but that the language doesn't guarantee. But not all pointers to functions have the same size (pointers to member functions can have different sizes based on the kind of inheritance the class uses), and void* is only required to be able to hold and recover pointers to objects.

    Here's an example of someone having problems because of it: http://stackoverflow.com/questions/29607359/size-of-pointer-to-member-function-varies-like-crazy

    but the language does allow it, and it does work.

    The language lets you break its rules, yes, but that doesn't mean that what you do when breaking them makes sense in the language. If it did, you wouldn't have had to tell the language that you were breaking the rules. You are relying on undefined behavior doing what you want it to do. The implementation could store a nullptr, or do a no-op when you store a function pointer in a void pointer, and then return whatever garbage it stored when you cast back, and the implementation would be abiding by the standard.



  • @cvi said in Enlightened:

    @djls45 said in Enlightened:

    And because I like to see how they might actually be used:
    

    Btw - I'd guess this was "designed" to be used with the CRTP, so might want to try that. ;-)

    Ah. That would allow construction of the object (only via new or a scoped variable, though), but it still allocates more memory if the derived class is new'd again, which still breaks the meaning of singleton and wipes out the previous state stored within the derived class's instance.



  • @djls45 It doesn't enforce the singletoniness, but at least it won't blow up in your face. (I mean, it's still useless, but at least it isn't actively dangerous that way.)

    Edit:

    @djls45 said in Enlightened:

    A pointer variable is simply a place to store a memory address. As such, any pointer can be treated as a pointer to any type, though in most cases that would not be useful. C/C++ allows it though, which makes it much easier to write things like self-modifying code.

    Unfortunately, that is not the case in C++. It is the case in most C++ implementations, but the C++ language standard has pretty strict rules regarding validity of pointers. From here:

    Violating Type Rules: It is undefined behavior to cast an int* to a float* and dereference it (accessing the "int" as if it were a "float"). C requires that these sorts of type conversions happen through memcpy: using pointer casts is not correct and undefined behavior results.

    and here:

    If the pointer P points to the ith element of an array, then the expressions P+n, n+P, and P-n are pointers of the same type that point to the i+nth, i+nth, and i-nth element of the same array, respectively. The result of pointer addition may also be a one-past-the-end pointer (that is, pointer P such that the expression P-1 points to the last element of the array). Any other situations (that is, attempts to generate a pointer that isn't pointing at an element of the same array or one past the end) invoke undefined behavior.

    (The above specifically is one of the issues that is being discussed on-and-off: it may not be possible to implement -for example- std::vector<> strictly within the bounds of standard C++, but one must rely on implementation defined behaviour.)

    Finally, I'm quite sure that C++ as a language does not have any concept of self-modifying code, that is outside of the language standard (and making that work relies on implementation-defined behaviour again).



  • @cvi said in Enlightened:

    Unfortunately, that is not the case in C++. It is the case in most C++ implementations, but the C++ language standard has pretty strict rules regarding validity of pointers. From here:

    Violating Type Rules: It is undefined behavior to cast an int* to a float* and dereference it (accessing the "int" as if it were a "float"). C requires that these sorts of type conversions happen through memcpy: using pointer casts is not correct and undefined behavior results.

    But then you have to consider why it's undefined behavior. int does not have a specific size, according to the standard, leaving it to be implementation-defined, which is usually the normal width of the general purpose CPU registers. int is usually 32-bits, but some (mostly older) implementations use 16 bits, and I think some newer ones use 64 bits. Therefore, the standard cannot guarantee what will exist at the memory address that the pointer refers to, if it is dereferenced as a pointer to a type other than its original definition. It still doesn't mean that the pointers themselves are different.

    and here:

    If the pointer P points to the ith element of an array, then the expressions P+n, n+P, and P-n are pointers of the same type that point to the i+nth, i+nth, and i-nth element of the same array, respectively. The result of pointer addition may also be a one-past-the-end pointer (that is, pointer P such that the expression P-1 points to the last element of the array). Any other situations (that is, attempts to generate a pointer that isn't pointing at an element of the same array or one past the end) invoke undefined behavior.

    Ah. Pointer arithmetic. Given T* P, P+n is treated by the compiler as "the address in P plus sizeof(T)*n". Again, attempting to derefence a pointer that does not point to an element within an array is undefined behavior because the standard cannot guarantee what will exist at that memory location.
    I'm not sure that sizeof(void) is defined, so pointer arithmetic with a void* is likely also undefined behavior.

    (The above specifically is one of the issues that is being discussed on-and-off: it may not be possible to implement -for example- std::vector<> strictly within the bounds of standard C++, but one must rely on implementation defined behaviour.)

    Does std::vector<> rely on undefined behavior??? This is the first I've heard of such a claim.

    Finally, I'm quite sure that C++ as a language does not have any concept of self-modifying code, that is outside of the language standard (and making that work relies on implementation-defined behaviour again).

    Does any language have a built-in conception of self-modifying code? That's the realm of the software author. And, yes, how exactly self-modifying code works will depend on what is available in the processor/implementation, so the programmer will have to take those into mind. My point is that pointers in C and C++ make it that much easier to write self-modifying code because they ultimately are just addresses into memory. Anything beyond that is dependent on either the type that the pointer points to or the implementation.

    Actually, that's not specific to C and C++. x86 and ARM Assembly both use this same definition, and I expect other languages that use pointers do, too.


  • Winner of the 2016 Presidential Election

    @djls45 said in Enlightened:

    But then you have to consider why it's undefined behavior. int does not have a specific size

    That's not the only reason for the strict aliasing rule. Otherwise, they could have made the behavior implementation-defined instead of undefined. The reason why this is undefined is because the compiler has to be able to assume that pointers of different, incompatible types cannot point to the same address to avoid unnecessary memory accesses.

    Think about it for a second: If the compiler wasn't allowed to make that assumption, it would have to generate assembly which also works if all pointers in scope point to the same memory address.



  • @asdf said in Enlightened:

    Think about it for a second: If the compiler wasn't allowed to make that assumption, it would have to generate assembly which also works if all pointers in scope point to the same memory address.

    But assembly can have all pointers refer to the same memory address. They won't all mean something useful (maybe none, maybe only one, maybe several), but they can.



  • @djls45 said in Enlightened:

    But then you have to consider why it's undefined behavior. int does not have a specific size, according to the standard, leaving it to be implementation-defined, which is usually the normal width of the general purpose CPU registers. int is usually 32-bits, but some (mostly older) implementations use 16 bits, and I think some newer ones use 64 bits. Therefore, the standard cannot guarantee what will exist at the memory address that the pointer refers to, if it is dereferenced as a pointer to a type other than its original definition. It still doesn't mean that the pointers themselves are different.

    If your point is that int and float may have different sizes, then you're missing the point of the author. Taking the address of an int and casting it to a float* and dereferencing that is illegal even if int and float have the same size (and alignment).

    If you do stuff like that, compilers are free to produce garbage. They won't do it out of spite, but the may do so inadvertently because of optimizations.

    Edit: Also, what @asdf said.

    Ah. Pointer arithmetic. Given T* P, P+n is treated by the compiler as "the address in P plus sizeof(T)*n". Again, attempting to derefence a pointer that does not point to an element within an array is undefined behavior because the standard cannot guarantee what will exist at that memory location.

    You're mixing up what your compiler happens to do, and what the language guarantees it to do. Either way, the above explanation says that attempting to generate a pointer outside of a valid object is UB, it doesn't say anything about dereferencing it.

    One example that sometimes is thrown around is that P+n may overflow (or underflow) the pointer value if the resulting pointer would be outside of the range [array start ... one-past-end of array]. The results of such an overflow/underflow are not specified by the standard. Again, your compiler + machine will probably deal with that just fine -- the pointer value will just wrap around, but the C++ standard doesn't require it to do so.

    I'm not sure that sizeof(void) is defined, so pointer arithmetic with a void* is likely also undefined behavior.

    Clang/MSVC error out. GCC issues a warning. Don't know what the standard says about it of the top of my head.

    Does std::vector<> rely on undefined behavior??? This is the first I've heard of such a claim.

    There's a couple of different issues related to this. See Core Issue 2182, Issue 1776 and the related Paper P0137. This post has another example. The topic pops up on r/cpp every now and then. Finally, there's std::launder (C++17), which is maybe kind-of an attempt at fixing the problem. You can probably find more if you go looking a bit (the list of active issues is a decent starting point).

    This doesn't mean that std::vector (or std::optional, which has similar "problems") doesn't work. It may just (possibly - if the issues are valid) be impossible to implement a portable one that doesn't rely on (common) implementation defined behaviour.

    My point is that pointers in C and C++ make it that much easier to write self-modifying code because they ultimately are just addresses into memory.

    The point is, according to the standard, they are not. In your implementation (and in most sensible ones), they are. There's a distinction. You may choose to ignore that distinction, but I think that you should at least be aware that you're doing something technically non-portable.

    FWIW, I'd also prefer if C++ (the language standard) was a bit closer to the metal with regards to its view on addressing and memory.

    Actually, that's not specific to C and C++. x86 and ARM Assembly both use this same definition, and I expect other languages that use pointers do, too.

    See above.


  • Winner of the 2016 Presidential Election

    @djls45 You're missing the point: Of course they can in assembly, but they're not allowed to in C++ to make sure that the compiler can perform sensible optimizations instead of having to generate brain-dead assembly whenever pointers are used.


  • Winner of the 2016 Presidential Election

    @cvi said in Enlightened:

    It may just (possibly - if the issues are valid) be impossible to implement a portable one that doesn't rely on (common) implementation defined behaviour.

    Relying on implementation-defined behavior when you're writing a standard library for a specific compiler is perfectly cromulent, though. Unlike relying on undefined behavior…



  • @asdf You're right. I guess I've been a bit sloppy with the terms. :-/

    At least some of the discussion revolves around whether or not you actually need to "rely on UB", and partially whether or not the standard contradicts itself.

    A parallel issue is that it'd be very nice if you didn't need to rely on implementation defined behaviour to (re-)implement containers (example: custom vectors of Folly and LLVM).

    Then there's pointers -- there's a lot of misconceptions about pointers. The gap between what the standard guarantees and what you (normally) get on flat-memory architectures (x86/ARM) is fairly large. And this occasionally blows into peoples faces. (E.g., operator< doesn't provide a total ordering of pointers, you need std::less for that. Checking if a pointer points into a certain memory block is apparently hard. And sometimes the rules are somewhat non-obvious.)


  • Discourse touched me in a no-no place

    @djls45 said in Enlightened:

    Does any language have a built-in conception of self-modifying code?

    Yes. It appears to be not too common when you're coming from the world of C and C++ (though technically, loading a dynamic library and calling functions in it will qualify), but it's a lot more prevalent in higher-level languages.


  • Java Dev

    @djls45 said in Enlightened:

    Ah. Pointer arithmetic. Given T* P, P+n is treated by the compiler as "the address in P plus sizeof(T)*n". Again, attempting to derefence a pointer that does not point to an element within an array is undefined behavior because the standard cannot guarantee what will exist at that memory location.

    The standard does not even guarantee it is a memory location. The result may be a bit pattern that is not a valid pointer. Or it may wrap at some 12-bit boundary in the middle of your 36-bit address space. Or, indeed, it may cause demons to come out of your nose.

    @djls45 said in Enlightened:

    I'm not sure that sizeof(void) is defined, so pointer arithmetic with a void* is likely also undefined behavior.

    Gcc starts yelling at you and you remember you need to use char * instead.


Log in to reply