Thread: Test C code: function call order

  1. #1
    Registered User
    Join Date
    Jun 2009
    Posts
    56

    Test C code: function call order

    Hello,

    I need to test the order in which some functions are called. Other than add a prinf in each function do you know any method/tool that can help me out?

    I normally use "check unit test framework" to test return values but I can't figure out if it can be used also in this case.
    Any idea?

    Thanks in advance

  2. #2
    and the hat of int overfl Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    39,660
    If you're taking about
    result = f() + g() * h();
    then the order in which the functions are called is unspecified.

    There is nothing to stop the compiler from generating code to call f() first, store the result in a temporary, before calling f() and g(), doing a multiply and finally adding the temp.
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.

  3. #3
    Registered User
    Join Date
    Jun 2005
    Posts
    6,815
    In the case mentioned by Salem, the way to guarantee an order or evaluation is to break the statement into multiple statements.

    For example, assuming the required call order is f() then g() then h().
    Code:
        result = f();
        result_of_g = g();
        result_of_h = h();
        result += result_of_g * result_of_h;
    If you want to test that f(), g(), and h() are called in the required order, the it is necessary to either step through with a debugger (eg set breakpoints in each function so you know execution will stop when any function is called) or set up some means by which each function can report it has been called (eg writing output to a log file or to stdout/stderr, appending an entry to a global linked list for later interrogation).

    The order of calling is only important if the functions have side-effects (for example, modifying a global) and the order of side-effects matters. It is generally a good idea to design your functions so they don't have such side effects, so the order of calls doesn't matter. If you do that, no need to test the order in which they are called.
    Right 98% of the time, and don't care about the other 3%.

    If I seem grumpy or unhelpful in reply to you, or tell you you need to demonstrate more effort before you can expect help, it is likely you deserve it. Suck it up, Buttercup, and read this, this, and this before posting again.

  4. #4
    Registered User ssharish2005's Avatar
    Join Date
    Sep 2005
    Location
    Cambridge, UK
    Posts
    1,732
    Well in addition to other explaination with regards, it may not be as the case that you would predict what the sequence of execution the compiler may pruse.
    But regardless of that, if you try step through the code with several breakpoint at each function using gdb. You could use the bracktrace feature to show the stack trace which shows the sequence of function calls taken to reach at the break point currently.

    But again it may be easy to just place debug prints in each function. Unless you require stack trace log at the runtime.

    ssharish
    Life is like riding a bicycle. To keep your balance you must keep moving - Einstein

  5. #5
    Registered User
    Join Date
    Jun 2009
    Posts
    56
    ok guys thank you for your reply.

    yea I think I'll go through printf or gdb with bt. I wanted to be sure about what is the "correct" way to do that

    Cheers

  6. #6
    Registered User
    Join Date
    Apr 2006
    Posts
    2,149
    Also, even in grumpy's code, the order of evaluation may not be fixed if f(), g(), and h() do not do IO, access atomics, mutexes, or volitile variables, or access the same memory location. Basically if the order of evaluation does not effect the visible output of a program it is still unspecified. Mutexes and atomics provide hard garuntees about the order of evaluation, IO and access to volatile provide a lesser guarantee and operations that do not do IO can still be moved around relative to them.
    It is too clear and so it is hard to see.
    A dunce once searched for fire with a lighted lantern.
    Had he known what fire was,
    He could have cooked his rice much sooner.

  7. #7
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    Quote Originally Posted by King Mir
    Also, even in grumpy's code, the order of evaluation may not be fixed if f(), g(), and h() do not do IO, access atomics, mutexes, or volitile variables, or access the same memory location. Basically if the order of evaluation does not effect the visible output of a program it is still unspecified.
    I see it differently: the order of evaluation is well defined, but the compiler is permitted to re-order or even remove operations during translation as long as it can determine that the net effect of the new sequence of operations and the net effect of the original operations with that order of evaluation remains the same.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  8. #8
    Registered User
    Join Date
    Apr 2006
    Posts
    2,149
    For IO that's saying the same thing in a different way. For C11 concurrency though, trying to define the guarantees provided my atomics and mutexes as an "effect" seems awkward at best. I suppose it's technically true for a well defined, deadlock free program, but in order to reason about if a program is well defined it's necessary to consider locks as stronger guarantees of sequence than the guarantees provided by statements in sequence.
    It is too clear and so it is hard to see.
    A dunce once searched for fire with a lighted lantern.
    Had he known what fire was,
    He could have cooked his rice much sooner.

  9. #9
    Registered User
    Join Date
    Jun 2005
    Posts
    6,815
    Sorry, King Mir, but you are mistaken.

    The standard (I'm paraphrasing here) requires that any observable effects of consecutive statements in a function with sequence points between them cannot be reordered. The only way of reordering those statements is if the compiler can unambiguously detect that reordering the statements cannot change the observable effects.

    Admittedly, some compilers do get a bit too aggressive with their optimisation, and break this rule. But that is a bug in the compiler (or optimiser) not something permitted by the standard.

    Atomics and mutexes used within the functions do not change that. If they did, it would not be possible to provide any guarantees that any threads within a multithreaded application behaved as the programmer intended.
    Right 98% of the time, and don't care about the other 3%.

    If I seem grumpy or unhelpful in reply to you, or tell you you need to demonstrate more effort before you can expect help, it is likely you deserve it. Suck it up, Buttercup, and read this, this, and this before posting again.

  10. #10
    Registered User
    Join Date
    Apr 2006
    Posts
    2,149
    Atomics and mutexes do change things, because an implementation (compiler or CPU) is not expected to guarantee that ordering optimisations don't change observable effects when accessing memory locations from multiple threads that are not ordered by way of an atomic or lock. Sequence points do not make it so that operations are seen in the same order by another thread. But a lock or atomic enforces an ordering between multithreaded operations, so that anything before a suitable signal on one thread is garunteed to happen before another thread reads the same signal.

    For a race condition free program it amounts to the same thing, but to reason about if a program is race condition free, this model of sequencing is necessary.
    Last edited by King Mir; 07-24-2012 at 11:44 AM.
    It is too clear and so it is hard to see.
    A dunce once searched for fire with a lighted lantern.
    Had he known what fire was,
    He could have cooked his rice much sooner.

  11. #11
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    5,108
    O_o

    I think you somehow misunderstood your own assertion.

    Code:
    result = f();
    result_of_g = g();
    result_of_h = h();
    result += result_of_g * result_of_h;
    If the compiler changes the order these functions are called within a thread atomic operations and locks do not reorder them in any way.

    If the compiler reorders sequence points for whatever reason you can't reason about the ultimate operation even without threads.

    When threads enter into the equation different threads may see the results of different shared operations at different times; atomic operations and locking can order the visibility of such results across threads.

    The point grumpy is making has nothing to do with threads and so threading primitives do not change anything.

    If we elaborate on the above example by pretending these facilities are actually locking primitives we can see more about what is going on with sequence points.

    Code:
    resultLock = fGetLock();
    result_of_gSharedData = gSharedUtility();
    result_of_hUnlockError = hUnlock(resultLock);
    If the compiler reorders these sequence points you can't reason about the code at all. You don't know if the result is thread safe. You don't know anything about the sequence of operations.

    Code:
    result_of_hUnlockError = hUnlock(resultLock);
    resultLock = fGetLock();
    result_of_gSharedData = gSharedUtility();
    This example is clearly broken. It doesn't matter if the code uses multiple threads. It is broken for a single thread.

    Soma

  12. #12
    Registered User
    Join Date
    Apr 2006
    Posts
    2,149
    Quote Originally Posted by phantomotap View Post
    If the compiler reorders sequence points for whatever reason you can't reason about the ultimate operation even without threads.
    I agree with all of what you said except this nitpick: That quote 's correct, as long as the order of IO and threading primatives is the same.

    But you can and must reason about the order of access to shared resources, and to do that you must treat locks and atomics as enforcing sequence.

    So given this example:
    Code:
    result_of_f = f();
    result_of_g = g();
    result_of_h = h();
    result = result_of_f * result_of_g * result_of_h;
    Assuming that f(),g(), and h() do not access thread primatives or IO, then the compiler can reorder the operations do do this:
    Code:
    result_of_h = h();
    result_of_g = g();
    result_of_f = f();
    result = result_of_f * result_of_g * result_of_h;
    However, if g, for example, modifies an atomic variable, then that's not allowed, because the access to the atomic guarantees that other threads see the write to result_of_f as happening before a read on the same atomic, and it guarantees that other thread see the write of result_of_h as happening after that read, (and therefore they cannot modify result_of_h after the read without a race condition -- the atomic write in g() can funtion like an aquire of the shared resource result_of_h).

    On the other hand if g() and only g() only does IO, then that reordering is legal. The order of IO stays the same. the visible result of the program may not be the same since the execution time of h() and f() may be different.

    Also, in addition to reordering, a cpu can and will do operations in parallel, these operations will take different amounts of time, and memory writes in particular may take longer from the point of view of another thread or process than from the same thread.
    Last edited by King Mir; 07-24-2012 at 11:57 AM.
    It is too clear and so it is hard to see.
    A dunce once searched for fire with a lighted lantern.
    Had he known what fire was,
    He could have cooked his rice much sooner.

  13. #13
    Registered User
    Join Date
    Mar 2011
    Posts
    546
    so you are saying if 'g()' modifies an atomic variable, then the compiler cannot reorder the sequence of calls? how does the compiler know what f(),g() and h() are doing if they are in a separate compilation unit?

    Code:
    result_of_f = f();
    result_of_g = g();
    result_of_h = h();
    result = result_of_f * result_of_g * result_of_h;

  14. #14
    Registered User
    Join Date
    Apr 2006
    Posts
    2,149
    Quote Originally Posted by dmh2000 View Post
    so you are saying if 'g()' modifies an atomic variable, then the compiler cannot reorder the sequence of calls? how does the compiler know what f(),g() and h() are doing if they are in a separate compilation unit?
    It has to either assume that any unresolvable function modifies an atomic, and therefore not reorder, or reorder when linking. Also, the CPU can reorder. Additionally, The C standard does not require the model of compile then link, and a compiler could be built that doesn't use it, which could remove this problem
    It is too clear and so it is hard to see.
    A dunce once searched for fire with a lighted lantern.
    Had he known what fire was,
    He could have cooked his rice much sooner.

  15. #15
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    5,108
    However, if g, for example, modifies an atomic variable, then that's not allowed, because the access to the atomic guarantees that other threads see the write to result_of_f as happening before a read on the same atomic, and it guarantees that other thread see the write of result_of_h as happening after that read, (and therefore they cannot modify result_of_h after the read without a race condition -- the atomic write in g() can funtion like an aquire of the shared resource result_of_h).
    An atomic write to an unrelated variable (let's call it `gatom') in `g' does not in any way guarantee that the write to `result_of_f' happens before a read from `result_of_f'.

    An atomic write to `gatom' does not in any way guarantee that a write to `result_of_f' happens before a read from `gatom'.

    An atomic read from `gatom' does not guarantee that a read from `result_of_h' happens after the read from `gatom'.

    An atomic read from `result_of_f' does not guarantee that a read from `result_of_h' happens after the read from `result_of_f'.

    An atomic variable only guarantees that reads and writes are completed in isolation. They do not impose any mutual or identity ordering. If one thread (`i') increments a variable (`v1') while another thread (`d') decrements a variable (`v2') the sole use of atomic reads and writes to the variables (`v1' and `v2') is insufficient to enforce a `v1++'->`v2--' pattern; it is entirely possible to get `v1++'->`v1++'->`v2--' as an ordering. This lack of any mutual or identity ordering holds for single threaded applications as well as applications with multiple threads.

    So, we wind up back at the compiler and sequence points. If the compiler doesn't recognize a sequence point, and so reorders, the use of atomic doesn't buy you correct ordering. Any given compiler may recognize any specific atomic operation as a sequence point, but that is not something enforced by the standards. (Well, I do not know C11 at all so it may be something in that standard.) If a compiler does recognize atomic reads and writes and necessary sequence points the compiler doesn't reorder so you get the canonical ordering.

    so you are saying if 'g()' modifies an atomic variable, then the compiler cannot reorder the sequence of calls?
    It has nothing to do with atomic variables. This is about sequence points. (C11 may specifically have mentioned atomic variables as sequence points.) According to the standard a compiler can not reorder any sequence point that matches certain criteria. This criteria includes calling standard "IO" functions (like `printf'), any operation with a `volatile' variable, mutating (writing) any object, and something to do with global variables that I can't really recall because almost every compiler ignores that bit.

    Of course, there is what the standard says and then there is what compilers do.

    how does the compiler know what f(),g() and h() are doing if they are in a separate compilation unit?
    Many compiler, by default, assume that functions beyond inspection always constitute necessary sequence points. This isn't mandated by the standard, but it is a practical approach.

    Some compiler, at higher optimization levels, can break code for any number of reasons; reordering necessary sequence points is one possible break.

    Soma

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Call managed code (c#) from unmanaged code (VC++ 6.0)
    By playxn in forum C++ Programming
    Replies: 3
    Last Post: 10-14-2008, 12:11 PM
  2. Replies: 14
    Last Post: 04-01-2008, 02:23 AM
  3. How do you order your game code?
    By Queatrix in forum A Brief History of Cprogramming.com
    Replies: 15
    Last Post: 02-05-2006, 06:26 PM
  4. using a driver function to test my code??
    By tommy69 in forum C Programming
    Replies: 24
    Last Post: 03-20-2004, 07:12 PM
  5. order of multiple test expression?
    By wolf in forum C Programming
    Replies: 5
    Last Post: 02-05-2002, 04:33 PM

Tags for this Thread