Trivial C11 threads.h wrapper (public domain)

Discussion in 'C Programming' started by John Tsiombikas, Sep 27, 2012.

  1. Hello everyone! It's been a long time since I was last on usenet :)

    I was anxious to use the new standard C11 threading features, instead of
    platform-specific APIs, but unfortunately my system libc (GNU libc) does
    not implement that bit that yet.
    So, I wrote a trivial C11 thread wrapper over POSIX threads, and
    released it in the public domain just in case anyone is itching to use
    standard C threading in a new project just like me:
    https://github.com/jtsiomb/c11threads

    The wrapper is so thin, I didn't think it made sense to make a proper
    library out of it, so it's just a header file with "static inline"
    functions. Drop it in your project, link with pthread and you're good to
    go.

    Disclaimer: I used a draft of the C11 standard while writting this code,
    since I don't actually have the final document. Feel free to notify me
    of any glaring discrepancies or omissions.

    Cheers!

    --
    John Tsiombikas
    http://nuclear.mutantstargoat.com/
     
    John Tsiombikas, Sep 27, 2012
    #1
    1. Advertising

  2. John Tsiombikas <> writes:
    [...]
    > Disclaimer: I used a draft of the C11 standard while writting this code,
    > since I don't actually have the final document. Feel free to notify me
    > of any glaring discrepancies or omissions.


    The latest draft I'm aware of is
    http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf
    I think it's nearly identical to the released standard.

    --
    Keith Thompson (The_Other_Keith) <http://www.ghoti.net/~kst>
    Will write code for food.
    "We must do something. This is something. Therefore, we must do this."
    -- Antony Jay and Jonathan Lynn, "Yes Minister"
     
    Keith Thompson, Sep 27, 2012
    #2
    1. Advertising

  3. On 2012-09-27, Keith Thompson <> wrote:
    > John Tsiombikas <> writes:
    > [...]
    >> Disclaimer: I used a draft of the C11 standard while writting this code,
    >> since I don't actually have the final document. Feel free to notify me
    >> of any glaring discrepancies or omissions.

    >
    > The latest draft I'm aware of is
    > http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf
    > I think it's nearly identical to the released standard.
    >


    Thanks, that's a bit more recent than the one I had. I'll go through
    them and see if I can spot any relevant differences.

    --
    John Tsiombikas
    http://nuclear.mutantstargoat.com/
     
    John Tsiombikas, Sep 27, 2012
    #3
  4. > So, I wrote a trivial C11 thread wrapper over POSIX threads, and
    > released it in the public domain just in case anyone is itching to use
    > standard C threading in a new project just like me:


    I have looked at your source file.
    https://github.com/jtsiomb/c11threads/blob/1fa0de734e204c1096d0dabac84d0c4497318944/c11threads.h

    I suggest to consider the following implementation details a bit more.
    1. Would you like to provide a "typedef" for each enumeration?

    2. How do you think about to complete error detection and corresponding
    exception handling?
    2.1 Mapping of error codes from the POSIX thread interface to the values that
    are specified by the current standard for the C programming language.
    2.2 pthread_mutexattr_init/destroy

    Regards,
    Markus
     
    Markus Elfring, Sep 27, 2012
    #4
  5. On 2012-09-27, Lorenzo Beretta <> wrote:
    > I haven't read it carefully enough to count as proofreading... most of
    > it seem to make sense, except for a couple of things:
    > 1. pthread_create can return EAGAIN, isn't it almost the same as thrd_nomem?


    I though about that for a bit while writting it, but after reading the
    pthreads manpage and the C standard a couple of times, I decided it's
    probably not the same error, and I thought it would be best to just
    leave thrd_nomem out. Might be wrong, I'll go through them again.

    > 2. pthread_mutex_timedlock sucks badly -- even microsoft got it right!
    > Try the following program and try not to scream when you realize
    > *why* it's not doing what you'd expect :)
    >
    > ...
    > const int s = 5;
    > ...
    > t.tv_nsec = 0;
    > t.tv_sec = s;
    > /* puke! */
    > printf("timed_lock(%d) = %d\n", s, pthread_mutex_timedlock(&m, &t));


    *gasp* ... it's absolute time ?! I must admit I never used
    timedlocks...

    I suppose that makes it easier to ask it to try locking the mutex
    until next friday at 3 o'clock or something :)


    --
    John Tsiombikas
    http://nuclear.mutantstargoat.com/
     
    John Tsiombikas, Sep 27, 2012
    #5
  6. On 2012-09-27, Markus Elfring <> wrote:
    >
    > I have looked at your source file.
    > https://github.com/jtsiomb/c11threads/blob/1fa0de734e204c1096d0dabac84d0c4497318944/c11threads.h
    >
    > I suggest to consider the following implementation details a bit more.
    > 1. Would you like to provide a "typedef" for each enumeration?


    Would I *like* to do that? probably not, but it's not a matter of
    preference. Unless I'm much mistaken, the C11 standard dictates int
    as the type of the second argument of mtx_init (where all the mtx_
    enumerations go), and as the return of all functions that need to
    indicate success or failure (thrd_ enumerations).

    > 2. How do you think about to complete error detection and corresponding
    > exception handling?
    > 2.1 Mapping of error codes from the POSIX thread interface to the values that
    > are specified by the current standard for the C programming language.


    I just went through both docs and tried to map pthread error codes to
    corresponding C11-mandated error codes. Those that had no counterpart, I
    just ignored, or bundled them all together under thrd_error as
    appropriate. Do you propose a different strategy?

    > 2.2 pthread_mutexattr_init/destroy


    Yes that was the most hairy bit. This mechanism doesn't seem to map
    exactly to what C11 wants. For instance the C standard talks about the
    possibility of having a mtx_timed | mtx_recursive mutex, but as far as
    I can tell with pthreads I can specify either PTHREAD_MUTEX_RECURSIVE or
    PTHREAD_MUTEX_TIMED_NP (which is not even standard), but not both.


    --
    John Tsiombikas
    http://nuclear.mutantstargoat.com/
     
    John Tsiombikas, Sep 27, 2012
    #6
  7. > ... probably not, but it's not a matter of preference.
    > Unless I'm much mistaken, the C11 standard dictates int as the type of
    > the second argument of mtx_init (where all the mtx_ enumerations go),
    > and as the return of all functions that need to indicate success
    > or failure (thrd_ enumerations).


    I suggest generally that a name is assigned to an enumeration. It might also be
    a nice service if a "typedef" will be provided for each enumeration so that it
    can become easier to reuse them as a data type in customised data structures.


    > I just went through both docs and tried to map pthread error codes to
    > corresponding C11-mandated error codes. Those that had no counterpart,
    > I just ignored, or bundled them all together under thrd_error as
    > appropriate. Do you propose a different strategy?


    I guess that this implementation detail shows a software design challenge. I do
    not like the ignorance of return values.


    > Yes that was the most hairy bit. This mechanism doesn't seem to map
    > exactly to what C11 wants. For instance the C standard talks about the
    > possibility of having a mtx_timed | mtx_recursive mutex, but as far as
    > I can tell with pthreads I can specify either PTHREAD_MUTEX_RECURSIVE or
    > PTHREAD_MUTEX_TIMED_NP (which is not even standard), but not both.


    Thanks that you pointed out such a portability issue.

    I would like to point out once again that your function call
    "pthread_mutexattr_init" shows the case of an unused return value. I would
    prefer complete error detection and corresponding exception handling.

    Some functions from the C standard API (like "mtx_destroy") have got the return
    type "void". But the called Pthreads function provides an error code eventually.
    How do you think about the reaction "abort()" in this use case?

    Regards,
    Markus
     
    Markus Elfring, Sep 28, 2012
    #7
  8. Markus Elfring <> writes:
    > John Tsiombikas <> writes:
    >> ... probably not, but it's not a matter of preference.
    >> Unless I'm much mistaken, the C11 standard dictates int as the type of
    >> the second argument of mtx_init (where all the mtx_ enumerations go),
    >> and as the return of all functions that need to indicate success
    >> or failure (thrd_ enumerations).

    >
    > I suggest generally that a name is assigned to an enumeration. It
    > might also be a nice service if a "typedef" will be provided for each
    > enumeration so that it can become easier to reuse them as a data type
    > in customised data structures.

    [...]

    The C standard doesn't provide typedefs for those enumerations.
    An implementation that provided one would be intruding on the
    user's namespace. And there's no particular reason for a typedef;
    enumeration constants are of type int, not of the enumeration type.

    --
    Keith Thompson (The_Other_Keith) <http://www.ghoti.net/~kst>
    Will write code for food.
    "We must do something. This is something. Therefore, we must do this."
    -- Antony Jay and Jonathan Lynn, "Yes Minister"
     
    Keith Thompson, Sep 28, 2012
    #8
  9. John Tsiombikas

    James Kuyper Guest

    On 09/28/2012 06:05 AM, Markus Elfring wrote:
    >> ... probably not, but it's not a matter of preference.
    >> Unless I'm much mistaken, the C11 standard dictates int as the type of
    >> the second argument of mtx_init (where all the mtx_ enumerations go),
    >> and as the return of all functions that need to indicate success
    >> or failure (thrd_ enumerations).

    >
    > I suggest generally that a name is assigned to an enumeration. It might also be
    > a nice service if a "typedef" will be provided for each enumeration so that it
    > can become easier to reuse them as a data type in customised data structures.


    He's trying to do the best that he can to implement a C99 substitute for
    a new feature of C2011. C2011 does NOT specify a typedef name (or even
    an enum tag) for the enumeration constants required to be declared in
    <threads.h>. An implementation could provide a tag or a typedef or both,
    but it can only use a reserved identifier for those purposes. If he used
    a reserved identifier for that purpose, it would make his C99 code have
    undefined behavior. If he used an identifier that was not reserved, his
    substitute would be inappropriately infringing on the user's name space.

    >> Yes that was the most hairy bit. This mechanism doesn't seem to map
    >> exactly to what C11 wants. For instance the C standard talks about the
    >> possibility of having a mtx_timed | mtx_recursive mutex, but as far as
    >> I can tell with pthreads I can specify either PTHREAD_MUTEX_RECURSIVE or
    >> PTHREAD_MUTEX_TIMED_NP (which is not even standard), but not both.

    >
    > Thanks that you pointed out such a portability issue.
    >
    > I would like to point out once again that your function call
    > "pthread_mutexattr_init" shows the case of an unused return value. I would
    > prefer complete error detection and corresponding exception handling.


    I agree - mtx_init() should return thrd_error if
    pthread_mutexattr_init() returns a non-zero value.

    > Some functions from the C standard API (like "mtx_destroy") have got the return
    > type "void". But the called Pthreads function provides an error code eventually.
    > How do you think about the reaction "abort()" in this use case?


    mtx_destroy() has, in itself, well-defined behavior that does not
    include calling abort(). However, pthread_mutex_destroy() is only
    allowed to fail if "The implementation has detected an attempt to
    destroy the object referenced by mutex while it is locked or referenced
    (for example, while being used in a pthread_cond_timedwait() or
    pthread_cond_wait()) by another thread."

    I haven't studied the C2011 threading specification carefully enough to
    be certain, but I suspect that if C code using this wrapper library
    could set up a situation in which pthread_mutex_destroy() would fail,
    that code which does so might itself have undefined behavior, according
    to C2011. If so, that would be sufficient to justify having
    mtx_destroy() call abort().
    --
    James Kuyper
     
    James Kuyper, Sep 28, 2012
    #9
  10. John Tsiombikas

    Jens Gustedt Guest

    Hello,

    Am 27.09.2012 05:38, schrieb John Tsiombikas:
    > Hello everyone! It's been a long time since I was last on usenet :)
    >
    > I was anxious to use the new standard C11 threading features, instead of
    > platform-specific APIs, but unfortunately my system libc (GNU libc) does
    > not implement that bit that yet.
    > So, I wrote a trivial C11 thread wrapper over POSIX threads, and
    > released it in the public domain just in case anyone is itching to use
    > standard C threading in a new project just like me:
    > https://github.com/jtsiomb/c11threads


    There is a major flaw in your implementation that concerns the type of
    the thread functions. You are simply casting C11 function type

    int f(void*)

    to the POSIX type

    void* f(void*)

    That is not only undefined behavior what is concerned C, but also
    dangerous. The calling conventions for these type of functions may be
    different on a given architecture, e.g returning the int value in a
    reserved register and the void* on the stack.

    Taking care of that incompatibility between C11 an POSIX threads needs
    a bit more care than that, I think, if you want to be portable.

    > The wrapper is so thin, I didn't think it made sense to make a proper
    > library out of it, so it's just a header file with "static inline"
    > functions. Drop it in your project, link with pthread and you're good to
    > go.
    >
    > Disclaimer: I used a draft of the C11 standard while writting this code,
    > since I don't actually have the final document. Feel free to notify me
    > of any glaring discrepancies or omissions.


    The xtime things are gone in the final version and all is now done
    with the time structures as they existed before.

    There is already an implementation of C11 threads as a wrapper around
    POSIX threads that is publicly available. It is integrated in P99:

    http://p99.gforge.inria.fr/

    The C11 compatibility wrapper of P99 should be completely interface
    compatible to C11 threads. (the include files are named differently,
    though.)

    Basically it also is a shallow wrapper using inline functions around
    POSIX, but in addition P99 also offers
    - an implementation/wrapper for the atomics
    - of thread local variables
    - _Generic (emulated through a macro)

    if they are available (generally through gcc and friends)

    Jens
     
    Jens Gustedt, Sep 28, 2012
    #10
  11. John Tsiombikas

    Kaz Kylheku Guest

    On 2012-09-27, John Tsiombikas <> wrote:
    > *gasp* ... it's absolute time ?! I must admit I never used
    > timedlocks...
    >
    > I suppose that makes it easier to ask it to try locking the mutex
    > until next friday at 3 o'clock or something :)


    What it allows you to do is resume an interrupted wait, so that the wakeup
    still happens at the originally predetermined time.

    With absolutely measured suspensions, you can make a thread wake up, say 10
    times a second regardless of the duration of the processing that takes place in
    betwen the wakeups (so long as it does not overrun the 0.1 second interval).
    In one hour, it will wake up exactly 36000 times.

    That kind of timing is sometimes required in real-time programming.
     
    Kaz Kylheku, Sep 28, 2012
    #11
  12. On 2012-09-28, James Kuyper <> wrote:
    >
    > I agree - mtx_init() should return thrd_error if
    > pthread_mutexattr_init() returns a non-zero value.


    Indeed, I overlooked that.

    --
    John Tsiombikas
    http://nuclear.mutantstargoat.com/
     
    John Tsiombikas, Sep 28, 2012
    #12
  13. John Tsiombikas

    Eric Sosman Guest

    On 9/27/2012 11:58 AM, John Tsiombikas wrote:
    > [...]
    > *gasp* ... it's absolute time ?! I must admit I never used
    > timedlocks...
    >
    > I suppose that makes it easier to ask it to try locking the mutex
    > until next friday at 3 o'clock or something :)


    "Or something." It certainly makes it easier to set up a
    timeout that will fire every dT milliseconds. Otherwise you'd
    have fun computing how much time to wait between "now" [HIGHER-
    PRIORITY THREAD RUNS] and [PAGE FAULT] "then."

    (Open)VMS had/has a handy feature: Time-related services
    accepted "absolute time" or "relative time" arguments, so the
    program could specify "for interval dT" or "until time T" for
    any kind of timeout. Also, the system's clock-adjusting code
    knew which kinds of waits were which, so "wait ten seconds"
    would wait for ten seconds regardless of adjustments, while
    "wait until 23:17:10" would pay attention to them.

    --
    Eric Sosman
    d
     
    Eric Sosman, Sep 28, 2012
    #13
  14. On Thu, 27 Sep 2012 18:27:19 +0200, Lorenzo Beretta <> wrote:
    >On 27/09/2012 17:58, John Tsiombikas wrote:
    >>> 2. pthread_mutex_timedlock sucks badly -- even microsoft got it right!
    >>> Try the following program and try not to scream when you realize
    >>> *why* it's not doing what you'd expect :)
    >>>
    >>> ...
    >>> const int s = 5;
    >>> ...
    >>> t.tv_nsec = 0;
    >>> t.tv_sec = s;
    >>> /* puke! */
    >>> printf("timed_lock(%d) = %d\n", s, pthread_mutex_timedlock(&m,&t));

    >>
    >> *gasp* ... it's absolute time ?! I must admit I never used
    >> timedlocks...
    >>
    >> I suppose that makes it easier to ask it to try locking the mutex
    >> until next friday at 3 o'clock or something :)


    > Lol yeah. And the best thing is that it seems that mtx_timedlock waits
    > "until after the TIME_UTC-based calendar time pointed to by ts" -- they
    > looked at pthread_mutex_timedlock and thought "nice! much better than
    > select(), poll(), WaifForSingleObjects(), epoll, kqueue, ..."


    Holy crap! This is evil(TM). I think I've used it at least once in the
    entirely wrong way thinking it works exactly like nanosleep timeouts!
     
    Giorgos Keramidas, Sep 29, 2012
    #14
  15. > There is already an implementation of C11 threads as a wrapper around
    > POSIX threads that is publicly available.


    I would interpret source code like the following as an update candidate.

    http://p99.gforge.inria.fr/p99-html/p99__threads_8h_source.html :
    ....
    00633 // 7.26.4 Mutex functions
    00634
    00638 p99_inline
    00639 void mtx_destroy(mtx_t *p00_mtx) {
    00640 (void)pthread_mutex_destroy(&P99_ENCP(p00_mtx));
    00641 }
    ....


    How do think about to get rid of the cast to the return type "void" in such use
    cases?

    Regards,
    Markus
     
    Markus Elfring, Sep 30, 2012
    #15
  16. Markus Elfring <> writes:
    >> There is already an implementation of C11 threads as a wrapper around
    >> POSIX threads that is publicly available.

    >
    > I would interpret source code like the following as an update candidate.


    I'm not sure what you mean by "update candidate".

    > http://p99.gforge.inria.fr/p99-html/p99__threads_8h_source.html :
    > ...
    > 00633 // 7.26.4 Mutex functions
    > 00634
    > 00638 p99_inline
    > 00639 void mtx_destroy(mtx_t *p00_mtx) {
    > 00640 (void)pthread_mutex_destroy(&P99_ENCP(p00_mtx));
    > 00641 }
    > ...
    >
    >
    > How do think about to get rid of the cast to the return type "void" in
    > such use cases?


    It doesn't matter much. The POSIX pthread_mutex_destroy() function
    returns an int result; the C11 mtx_destroy() function returns void.
    The (void) cast in this particular implementation is probably
    there to silence a compiler warning about the result of the call
    being discarded. The semantics are exactly the same with or without
    the cast.

    Could you be reading more into this than is actually there?

    --
    Keith Thompson (The_Other_Keith) <http://www.ghoti.net/~kst>
    Will write code for food.
    "We must do something. This is something. Therefore, we must do this."
    -- Antony Jay and Jonathan Lynn, "Yes Minister"
     
    Keith Thompson, Sep 30, 2012
    #16
  17. John Tsiombikas

    Jens Gustedt Guest

    Am 30.09.2012 23:28, schrieb Keith Thompson:
    > Markus Elfring <> writes:
    >>> There is already an implementation of C11 threads as a wrapper around
    >>> POSIX threads that is publicly available.

    >>
    >> I would interpret source code like the following as an update candidate.

    >
    > I'm not sure what you mean by "update candidate".
    >
    >> http://p99.gforge.inria.fr/p99-html/p99__threads_8h_source.html :
    >> ...
    >> 00633 // 7.26.4 Mutex functions
    >> 00634
    >> 00638 p99_inline
    >> 00639 void mtx_destroy(mtx_t *p00_mtx) {
    >> 00640 (void)pthread_mutex_destroy(&P99_ENCP(p00_mtx));
    >> 00641 }
    >> ...
    >>
    >>
    >> How do think about to get rid of the cast to the return type "void" in
    >> such use cases?

    >
    > It doesn't matter much. The POSIX pthread_mutex_destroy() function
    > returns an int result; the C11 mtx_destroy() function returns void.
    > The (void) cast in this particular implementation is probably
    > there to silence a compiler warning about the result of the call
    > being discarded. The semantics are exactly the same with or without
    > the cast.


    Exactly, in particular there are some linux systems where the headers
    are annotated with gcc extensions that make it difficult to ignore the
    return of a system function.

    And if you (Markus) refer to the fact of "casting a return type", this
    is something completely different from "casting a function pointer to
    a function type with a different return type". In one case the
    compiler is supposed to convert the actual return type into something
    different (with well defined rules) and in the other case you would
    trick the compiler to believe that the return type would be of a
    certain type (live on the stack, in a certain hardware register).

    In the particular case of a cast with "(void)" has a special semantic
    in the standard (here C11), namely to ignore the value an to evaluate
    the expression just for its side effects:

    > 6.3.2.2 void ... If an expression of any other type is evaluated as
    > a void expression, its value or designator is discarded. (A void
    > expression is evaluated for its side effects.)


    and then in an example it even explains

    > If a function call is evaluated as an expression statement for its
    > side effects only, the discarding of its value may be made explicit by
    > converting the expression to a void expression by means of a cast


    And then, finally, the C11 standard leaves no way to get semantic of a
    failed call accros, here. Do you see a way to use the return value in
    some way?

    Jens
     
    Jens Gustedt, Sep 30, 2012
    #17
  18. John Tsiombikas

    Jens Gustedt Guest

    Am 01.10.2012 00:37, schrieb Jens Gustedt:
    > Am 30.09.2012 23:28, schrieb Keith Thompson:
    >> Markus Elfring <> writes:


    >> 00633 // 7.26.4 Mutex functions
    >>> 00634
    >>> 00638 p99_inline
    >>> 00639 void mtx_destroy(mtx_t *p00_mtx) {
    >>> 00640 (void)pthread_mutex_destroy(&P99_ENCP(p00_mtx));
    >>> 00641 }


    > And then, finally, the C11 standard leaves no way to get semantic of a
    > failed call accros, here. Do you see a way to use the return value in
    > some way?


    Ah, thinking of it a bit more, it is actually not "(void)" which is
    problematic but the fact that the function may result in errno being
    set to a non-zero value. I'll replace by something like

    if (pthread_mutex_destroy(&P99_ENCP(p00_mtx))) errno = 0;

    Thanks
    Jens
     
    Jens Gustedt, Sep 30, 2012
    #18
  19. John Tsiombikas

    Eric Sosman Guest

    On 9/30/2012 6:44 PM, Jens Gustedt wrote:
    >[...]
    > Ah, thinking of it a bit more, it is actually not "(void)" which is
    > problematic but the fact that the function may result in errno being
    > set to a non-zero value. I'll replace by something like
    >
    > if (pthread_mutex_destroy(&P99_ENCP(p00_mtx))) errno = 0;


    This would be a very bad thing to do in an implementation
    that is supposed to imitate a Standard C library function.

    7.5p3: "The value of errno in the initial thread is zero
    at program startup [...] but is never set to zero by any
    library function. [...]"

    --
    Eric Sosman
    d
     
    Eric Sosman, Oct 1, 2012
    #19
  20. John Tsiombikas

    Jens Gustedt Guest

    Am 01.10.2012 01:48, schrieb Eric Sosman:
    > On 9/30/2012 6:44 PM, Jens Gustedt wrote:
    >> [...]
    >> Ah, thinking of it a bit more, it is actually not "(void)" which is
    >> problematic but the fact that the function may result in errno being
    >> set to a non-zero value. I'll replace by something like
    >>
    >> if (pthread_mutex_destroy(&P99_ENCP(p00_mtx))) errno = 0;

    >
    > This would be a very bad thing to do in an implementation
    > that is supposed to imitate a Standard C library function.
    >
    > 7.5p3: "The value of errno in the initial thread is zero
    > at program startup [...] but is never set to zero by any
    > library function. [...]"
    >


    Right. The reason is that I thought that pthread_mutex_destroy could
    set errno to some error value. But re-reading the manual page of that
    I see that the error code is in the function return. So there is no
    need to do that at all.

    (Otherwise I would have had to capture errno before the call and to
    restore it if the call to pthread_mutex_destroy failed. Expensive.)

    Thanks

    Jens
     
    Jens Gustedt, Oct 1, 2012
    #20
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Charles A. Lackman
    Replies:
    1
    Views:
    1,423
    smith
    Dec 8, 2004
  2. SpamProof
    Replies:
    0
    Views:
    618
    SpamProof
    Oct 21, 2003
  3. baibaichen

    trivial or non-trivial object

    baibaichen, Jan 12, 2006, in forum: C++
    Replies:
    3
    Views:
    941
    osmium
    Jan 12, 2006
  4. Ioannis Vranos

    C11 reference book

    Ioannis Vranos, Jan 3, 2012, in forum: C Programming
    Replies:
    5
    Views:
    2,949
    James Kuyper
    Jan 5, 2012
  5. Markus Elfring
    Replies:
    0
    Views:
    298
    Markus Elfring
    Oct 12, 2012
Loading...

Share This Page