Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • World
  • Users
  • Groups
Skins
  • Light
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dark
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • Default (No Skin)
  • No Skin
Collapse
Code Project
  1. Home
  2. General Programming
  3. C#
  4. C# versus C++, null references and protecting against them

C# versus C++, null references and protecting against them

Scheduled Pinned Locked Moved C#
csharpc++
23 Posts 6 Posters 0 Views 1 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • C Chuck OToole

    Hi. I don't think that passing as a reference really gets rid of the need for null checks. You can satisfy the rule for an actual object by dereferencing a pointer. That satisfies the compiler but the pointer could still be NULL. Consider this working C++ code.

    void NameList::Append(NameList *ToBeAdded)
    {
    Members->Append(*ToBeAdded->Members);
    ToBeAdded->Members->RemoveAll();
    }

    where "Members" is defined as

    CObArray *Members

    and the CObArray member Append is defined as

    INT_PTR Append(const CObArray& src);

    so if the "ToBeAdded" Namelist Object has a Members field that contains NULL, the compiler is OK with that and the Append function should really do ASSERT and/or runtime checks. In any event, I don't think you can rely on the & to protect against somebody passing NULL. Chuck (45 years coding and counting)

    B Offline
    B Offline
    bob16972
    wrote on last edited by
    #13

    Are you referring to what is described in the C++ Specification, Section 8.3.3, page 179 ? "A reference shall be initialized to refer to a valid object or function. [ Note: in particular, a null reference cannot exist in a well-defined program, because the only way to create such a reference would be to bind it to the “object” obtained by dereferencing a null pointer, which causes undefined behavior. As described in 9.6, a reference cannot be bound directly to a bit-field. —end note ]" Your point is taken but I think in the case you described, the violation occurs by the caller and before the prolog of the method completes. If the caller violates the standard definition of a reference, there is nothing I can do about it. I'm not even sure there is a way to check the reference for validity since the result of the action you described would be undefined (or at least, so it seems).

    C P 2 Replies Last reply
    0
    • B bob16972

      Are you referring to what is described in the C++ Specification, Section 8.3.3, page 179 ? "A reference shall be initialized to refer to a valid object or function. [ Note: in particular, a null reference cannot exist in a well-defined program, because the only way to create such a reference would be to bind it to the “object” obtained by dereferencing a null pointer, which causes undefined behavior. As described in 9.6, a reference cannot be bound directly to a bit-field. —end note ]" Your point is taken but I think in the case you described, the violation occurs by the caller and before the prolog of the method completes. If the caller violates the standard definition of a reference, there is nothing I can do about it. I'm not even sure there is a way to check the reference for validity since the result of the action you described would be undefined (or at least, so it seems).

      C Offline
      C Offline
      Chuck OToole
      wrote on last edited by
      #14

      Well, I was simply reacting to your statement in the original post,

      /*
      No need to check the reference before using it
      since references cannot be null.
      */

      In my experience, when some code assumes things cannot be null, that's exactly where to start looking for bugs. And if you're assuming that a "by reference" specification removes some level of bug enough that you can remove the ASSERT / checks, then I wanted to show an example (real, not concocted) that shows the assumption to be false. And then you asked for a way to eliminate such checks from C# code like you could from C++ code, which I infer to mean that you have such checks, find them somewhat inefficient and/or redundant in well behaved programs, and would like to remove them using some help from the language. Again, I wanted to point out that the C++ language does not give you any help either and that your trust in it was misplaced. Now your reply says

      bob16972 wrote:

      the violation occurs by the caller and before the prolog of the method completes. If the caller violates the standard definition of a reference, there is nothing I can do about it. I'm not even sure there is a way to check the reference for validity since the result of the action you described would be undefined (or at least, so it seems).

      which seems like an reasonable argument to remove all checks anyway since there's nothing you can do when the caller violates some rules, including the assumed rule that they'll pass something reasonable. Look, all I'm saying is that if you are writing public API entries, for other programmers to call, you're better off including the checks all the time. Yes, "null pointers" are just one element in the set of all possible bad arguments and checking for them will never catch the case of the pointer being 0x00000001 or 0xcdcdcdcd (assuming 32-bit pointers) but ASSERTs may catch enough during debugging that may make debugging easier.

      B 1 Reply Last reply
      0
      • B bob16972

        Are you referring to what is described in the C++ Specification, Section 8.3.3, page 179 ? "A reference shall be initialized to refer to a valid object or function. [ Note: in particular, a null reference cannot exist in a well-defined program, because the only way to create such a reference would be to bind it to the “object” obtained by dereferencing a null pointer, which causes undefined behavior. As described in 9.6, a reference cannot be bound directly to a bit-field. —end note ]" Your point is taken but I think in the case you described, the violation occurs by the caller and before the prolog of the method completes. If the caller violates the standard definition of a reference, there is nothing I can do about it. I'm not even sure there is a way to check the reference for validity since the result of the action you described would be undefined (or at least, so it seems).

        P Offline
        P Offline
        Paul Michalik
        wrote on last edited by
        #15

        bob16972 wrote:

        If the caller violates the standard definition of a reference, there is nothing I can do about it. I'm not even sure there is a way to check the reference for validity since the result of the action you described would be undefined (or at least, so it seems).

        Exactly, with no need to further qualify the statement as you did in the second sentence. And no, there is no way to check a reference for validity, since it must be valid by definition. Otherwise, the caller has violated (a) the language specification of a valid C++ program and (b) the contract you have specified by defining the method signature that way... That's what I wrote in my post above (which was down-voted for some reason).

        B 1 Reply Last reply
        0
        • P Paul Michalik

          bob16972 wrote:

          If the caller violates the standard definition of a reference, there is nothing I can do about it. I'm not even sure there is a way to check the reference for validity since the result of the action you described would be undefined (or at least, so it seems).

          Exactly, with no need to further qualify the statement as you did in the second sentence. And no, there is no way to check a reference for validity, since it must be valid by definition. Otherwise, the caller has violated (a) the language specification of a valid C++ program and (b) the contract you have specified by defining the method signature that way... That's what I wrote in my post above (which was down-voted for some reason).

          B Offline
          B Offline
          bob16972
          wrote on last edited by
          #16

          Jeez. You missed the whole point of what I'm trying to do. You keep focusing on C++ references and pointers whereas I have no problem with C++ since I can choose to use pointers when I want to allow null and a reference when I don't. In C# sharp, I don't seem to have that option (except for ref apparently which would require a const to (attempt to) protect the caller from the method changing the reference on them. From reading your earlier post, you apparently didn't read the code comments in my code examples as I had already stated that a reference can't be null but you tried to explain that very thing to me which would indicate you missed the point and the question. Regardless, I appreciate your attempt to help find a solution, for what it's worth. thanks.

          1 Reply Last reply
          0
          • C Chuck OToole

            Well, I was simply reacting to your statement in the original post,

            /*
            No need to check the reference before using it
            since references cannot be null.
            */

            In my experience, when some code assumes things cannot be null, that's exactly where to start looking for bugs. And if you're assuming that a "by reference" specification removes some level of bug enough that you can remove the ASSERT / checks, then I wanted to show an example (real, not concocted) that shows the assumption to be false. And then you asked for a way to eliminate such checks from C# code like you could from C++ code, which I infer to mean that you have such checks, find them somewhat inefficient and/or redundant in well behaved programs, and would like to remove them using some help from the language. Again, I wanted to point out that the C++ language does not give you any help either and that your trust in it was misplaced. Now your reply says

            bob16972 wrote:

            the violation occurs by the caller and before the prolog of the method completes. If the caller violates the standard definition of a reference, there is nothing I can do about it. I'm not even sure there is a way to check the reference for validity since the result of the action you described would be undefined (or at least, so it seems).

            which seems like an reasonable argument to remove all checks anyway since there's nothing you can do when the caller violates some rules, including the assumed rule that they'll pass something reasonable. Look, all I'm saying is that if you are writing public API entries, for other programmers to call, you're better off including the checks all the time. Yes, "null pointers" are just one element in the set of all possible bad arguments and checking for them will never catch the case of the pointer being 0x00000001 or 0xcdcdcdcd (assuming 32-bit pointers) but ASSERTs may catch enough during debugging that may make debugging easier.

            B Offline
            B Offline
            bob16972
            wrote on last edited by
            #17

            I agree that if a legal value can be null, always check it first. However, for a C++ reference, the rules don't provide for a legal null value so its not a valid option for the caller. In C++, by definition, you cannot have a null reference. If the caller tries to do anything that dereferences a null pointer, they have violated the rules and will probably crash the program before the function or method call. What you are proposing is that I can somehow protect against the caller crashing the program before they even enter the method or function. Think about it. your suggesting the caller can access memory location NULL, which for sake of argument is 0 in MFC, which results in an attempt to get the value at virtual memory location 0. In windows, attempting access any virtual memory address between 0-65535 will cause a violation. My function or method would not even be involved at that point so I would hope you would grant that there is no need to protect against something that can never get past the threshold of my functions prologue. If you won't grant me that argument, then I'd be interested to hear the rebuttal as I don't see how it could get into my function in the first place before being stuffed by the OS. Regardless, thanks for the comments as they have been thought provoking and interesting and always welcome.

            C 1 Reply Last reply
            0
            • B bob16972

              I agree that if a legal value can be null, always check it first. However, for a C++ reference, the rules don't provide for a legal null value so its not a valid option for the caller. In C++, by definition, you cannot have a null reference. If the caller tries to do anything that dereferences a null pointer, they have violated the rules and will probably crash the program before the function or method call. What you are proposing is that I can somehow protect against the caller crashing the program before they even enter the method or function. Think about it. your suggesting the caller can access memory location NULL, which for sake of argument is 0 in MFC, which results in an attempt to get the value at virtual memory location 0. In windows, attempting access any virtual memory address between 0-65535 will cause a violation. My function or method would not even be involved at that point so I would hope you would grant that there is no need to protect against something that can never get past the threshold of my functions prologue. If you won't grant me that argument, then I'd be interested to hear the rebuttal as I don't see how it could get into my function in the first place before being stuffed by the OS. Regardless, thanks for the comments as they have been thought provoking and interesting and always welcome.

              C Offline
              C Offline
              Chuck OToole
              wrote on last edited by
              #18

              I'm afraid you missed a subtle point in my example, the point that gets you into your function with a NULL value. I've simplified it below

              void Bar(CObArray &ar)
              {
              int foo = ar.GetCount();
              }

              void Foo(void)
              {
              CObArray *bob = NULL;

              Bar(\*bob);
              

              }

              set a breakpoint on the "int foo = ar.GetCount()" and observe that ar cannot be examined in the debugger, it's value is null. Note that you do in fact get to that statement, that is, the "Bar(*bob)" in the function "Foo" does not throw an exception. Although it looks like it is dereferencing the clearly null pointer, it's really just creating the argument for Bar by getting the address of the CObject and passing it, that is, making a "by reference" value for the Bar function. Therefore, the "crash" happens in "Bar" (i.e. your function) rather than at the invocation site. In my first reply, I was showing that the "ToBeAdded" object, which was valid, had a "Members" data member that was, in fact, NULL and that there was no "help" from the compiler / language rules that prevented it from passing NULL, even though the rules appear to say that's illegal. So, my conclusion was that if you don't want to allow NULL as an input to your functions and that you used to explicitly ASSERT / check for that case, you need to continue to check for that case because simply using "&" to eliminate that possibility was not going to work. Thus ends my rebuttal :)

              P B 2 Replies Last reply
              0
              • C Chuck OToole

                I'm afraid you missed a subtle point in my example, the point that gets you into your function with a NULL value. I've simplified it below

                void Bar(CObArray &ar)
                {
                int foo = ar.GetCount();
                }

                void Foo(void)
                {
                CObArray *bob = NULL;

                Bar(\*bob);
                

                }

                set a breakpoint on the "int foo = ar.GetCount()" and observe that ar cannot be examined in the debugger, it's value is null. Note that you do in fact get to that statement, that is, the "Bar(*bob)" in the function "Foo" does not throw an exception. Although it looks like it is dereferencing the clearly null pointer, it's really just creating the argument for Bar by getting the address of the CObject and passing it, that is, making a "by reference" value for the Bar function. Therefore, the "crash" happens in "Bar" (i.e. your function) rather than at the invocation site. In my first reply, I was showing that the "ToBeAdded" object, which was valid, had a "Members" data member that was, in fact, NULL and that there was no "help" from the compiler / language rules that prevented it from passing NULL, even though the rules appear to say that's illegal. So, my conclusion was that if you don't want to allow NULL as an input to your functions and that you used to explicitly ASSERT / check for that case, you need to continue to check for that case because simply using "&" to eliminate that possibility was not going to work. Thus ends my rebuttal :)

                P Offline
                P Offline
                Paul Michalik
                wrote on last edited by
                #19

                What you describe is Microsoft (implementation) specific version of "undefined behavior". The code in Foo is ill-formed and any assumptions about what happens after you took a reference to a "null" pointer are worthless...(the lang. spec. does not require an exception to be thrown.)

                C 2 Replies Last reply
                0
                • P Paul Michalik

                  What you describe is Microsoft (implementation) specific version of "undefined behavior". The code in Foo is ill-formed and any assumptions about what happens after you took a reference to a "null" pointer are worthless...(the lang. spec. does not require an exception to be thrown.)

                  C Offline
                  C Offline
                  Chuck OToole
                  wrote on last edited by
                  #20

                  Well, that is true to some extent (the undefined behavior part, not the malformed part :)) Unfortunately, I don't live in the happy world where I can rely on everybody following the rules or where I can hold up a standard and claim it's not my fault that my code blew up. I live in a world, somewhat dominated by Microsoft whose implementation details have real effects, where my libraries, reusable modules, etc are to be used by other programmers and they will often have bugs. I try my best to prevent their bugs from crashing my code but instead give them a reasonable exception or other error indicator so they can find their own bugs without calling me to say "your code crashed". Perhaps this is ingrained in me from my early years of operating system development where a kernel crash was the worst possible thing regardless of what the "user" did to it via the APIs. Imagine how angry people would get if simple calls to the base Windows APIs would cause blue screens all the time.

                  paul_71 wrote:

                  any assumptions about what happens after you took a reference to a "null" pointer are worthless

                  And this is exactly my point to the OP, his assumption that one cannot get into his code with a null object reference by using "&" is also worthless (your words) as I can clearly demonstrate that you can. In any event, I will continue to challenge the assertion that one can remove defensive code because "by definition it cannot happen".

                  1 Reply Last reply
                  0
                  • P Paul Michalik

                    What you describe is Microsoft (implementation) specific version of "undefined behavior". The code in Foo is ill-formed and any assumptions about what happens after you took a reference to a "null" pointer are worthless...(the lang. spec. does not require an exception to be thrown.)

                    C Offline
                    C Offline
                    Chuck OToole
                    wrote on last edited by
                    #21

                    PS, it seems they've been arguing about this exact syntax for quite some time now. it appears there are two camps in the standard, one that wants it be defined behavior and one that does. The following like should provide some interesting reading on the subject. http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#232

                    1 Reply Last reply
                    0
                    • C Chuck OToole

                      I'm afraid you missed a subtle point in my example, the point that gets you into your function with a NULL value. I've simplified it below

                      void Bar(CObArray &ar)
                      {
                      int foo = ar.GetCount();
                      }

                      void Foo(void)
                      {
                      CObArray *bob = NULL;

                      Bar(\*bob);
                      

                      }

                      set a breakpoint on the "int foo = ar.GetCount()" and observe that ar cannot be examined in the debugger, it's value is null. Note that you do in fact get to that statement, that is, the "Bar(*bob)" in the function "Foo" does not throw an exception. Although it looks like it is dereferencing the clearly null pointer, it's really just creating the argument for Bar by getting the address of the CObject and passing it, that is, making a "by reference" value for the Bar function. Therefore, the "crash" happens in "Bar" (i.e. your function) rather than at the invocation site. In my first reply, I was showing that the "ToBeAdded" object, which was valid, had a "Members" data member that was, in fact, NULL and that there was no "help" from the compiler / language rules that prevented it from passing NULL, even though the rules appear to say that's illegal. So, my conclusion was that if you don't want to allow NULL as an input to your functions and that you used to explicitly ASSERT / check for that case, you need to continue to check for that case because simply using "&" to eliminate that possibility was not going to work. Thus ends my rebuttal :)

                      B Offline
                      B Offline
                      bob16972
                      wrote on last edited by
                      #22

                      Ouch. I stand corrected. I started the day sure the sky was blue and now I'm left wondering if their implemetation ignores the statement "must be initialized by a valid object" and the part on page 198, section 8.5.3... "...Argument passing (5.2.2) and function value return (6.6.3) are initializations." But I agree (this includes some of your statements in another path of this thread as well), that I'm stuck with their implementation. I noticed they even let me dot off the invalid reference and call methods without any exceptions. At first I was shocked, but now I'm quite intrigued to see if any compiler vendor can implement this correctly. I'm glad I never had to go up against you in debate class. :) Excuse me while I go fix a million lines of active code. ;P

                      P 1 Reply Last reply
                      0
                      • B bob16972

                        Ouch. I stand corrected. I started the day sure the sky was blue and now I'm left wondering if their implemetation ignores the statement "must be initialized by a valid object" and the part on page 198, section 8.5.3... "...Argument passing (5.2.2) and function value return (6.6.3) are initializations." But I agree (this includes some of your statements in another path of this thread as well), that I'm stuck with their implementation. I noticed they even let me dot off the invalid reference and call methods without any exceptions. At first I was shocked, but now I'm quite intrigued to see if any compiler vendor can implement this correctly. I'm glad I never had to go up against you in debate class. :) Excuse me while I go fix a million lines of active code. ;P

                        P Offline
                        P Offline
                        Paul Michalik
                        wrote on last edited by
                        #23

                        Hm, not sure to whom this may concern, I always get confused by these hierarchies of threads... Anyway, the concept of a C++ reference is a subtle thing and it got even subtler with c++0x, as you might know. For good reasons this concept wasn't considered is what C# and other CLR targeting languages - and now we're back at OP's problem (at least as I see it...): in case of reference types (and in a verifiable program), you always deal with handles ("pointers") to instances. These handles can have two states: (a) pointing to valid object, (b) or "null". You can take a value of a "handle" by reference, by stating this intent explicitely at both call and target sites. However, this does not change the semantics of a handle, you just get access to the location of whatever the handle represents. So in C#, if you want to protect against handle pointing to "null", you either have to be explicit about that (code contracts are a great way to do that), or you rely on NullArgumentException handling (which is guaranteed to be thrown by the specs). The ref modifier mentioned somewhere in the thread does not help much:

                        void Bar(MyClass pParam) {
                        pParam.Member(); // guaranteed NullArgument exception if null
                        }

                        void Bar(ref MyClass pParam) {
                        pParam.Member(); // guaranteed NullArgument exception if null
                        pParam = new MyClass();
                        }

                        void Foo() {
                        MyClass tI1;
                        MyClass tI2 = null;
                        MyClass tI3 = new MyClass();

                        Bar(ref tI1); // error, tI1 uninitialized
                        Bar(ref tI2); // ok
                        Bar(ref tI3); // ok
                        
                        Bar(tI1); // warning or error, not sure
                        Bar(tI2); // NullArgumentEx...
                        Bar(tI3); // ok
                        

                        }

                        1 Reply Last reply
                        0
                        Reply
                        • Reply as topic
                        Log in to reply
                        • Oldest to Newest
                        • Newest to Oldest
                        • Most Votes


                        • Login

                        • Don't have an account? Register

                        • Login or register to search.
                        • First post
                          Last post
                        0
                        • Categories
                        • Recent
                        • Tags
                        • Popular
                        • World
                        • Users
                        • Groups