8

I've been looking at the GCC docs for defining macros and it looks like what I want isn't possible, but I figure if it is, someone here would know.

What I want to do is define this macro:

synchronized(x) {
  do_thing();
}

Which expands to:

{
    pthread_mutex_lock(&x);
    do_thing();
    pthread_mutex_unlock(&x);
}

In C++ I could just make a SynchronizedBlock object that gets the lock in its constructor and unlocks in the destructor, but I have no idea how to do it in C.

I realize I could use a function pointer in the form synchronized(x, &myfunction);, but my goal is to make some C code look as much like Java as possible. And yes, I know this is evil.

Jens Gustedt
  • 76,821
  • 6
  • 102
  • 177
Brendan Long
  • 53,280
  • 21
  • 146
  • 188
  • 4
    You really should think twice before trying to make one language look like another. A macro that looked like the syntax you're asking for would be extremely confusing to someone reading it as C. – R.. GitHub STOP HELPING ICE Oct 27 '10 at 19:54
  • R. is right that this might make things difficult to read, since it is not easily visible that this is a macro. I think the best is to stick to the convention that macros should be uppercase, and that the name should be really informative. I'd go for something like `MUTUALY_EXCLUDE` or so. – Jens Gustedt Oct 27 '10 at 21:11
  • ^ `And yes, I know this is evil.` – Brendan Long Oct 27 '10 at 21:47

4 Answers4

17

EDIT: Changed to nategoose's version

#define synchronized(lock) \
for (pthread_mutex_t * i_#lock = &lock; i_#lock; \
     i_#lock = NULL, pthread_mutex_unlock(i_#lock)) \
    for (pthread_mutex_lock(i_#lock); i_#lock; i_#lock = NULL)

And you can use it like this:

synchronized(x) {
    do_thing(x);
}

Or even without braces

synchronized(x)
    do_thing();
Joe D
  • 2,855
  • 2
  • 31
  • 25
  • Very impressive. Even the line numbers in errors would come out right. The only real faults is that the lock argument shouldn't appear twice within the macro and you left out most of your trailing `\\`s – nategoose Oct 27 '10 at 20:25
  • 5
    That is simultaneously amazing and horrifying. I never would've thought of that. – Brendan Long Oct 27 '10 at 20:28
  • 3
    This should fix both: `#define synchronized(lock)` \ `for (pthread_mutex_t * i_ = &lock; i_; i_ = NULL)` \ ` for (pthread_mutex_lock(i_); i_;` \ ` pthread_mutex_unlock(i_), i_ = NULL)` – nategoose Oct 27 '10 at 20:30
  • 2
    or, with a single `for()` and a check if the lock has been aquired: `#define synchronized(MUTEX) \ for(pthread_mutex_t *synchronized_mutex_ = &MUTEX; \ synchronized_mutex_ && !pthread_mutex_lock(synchronized_mutex_); \ pthread_mutex_unlock(synchronized_mutex_), synchronized_mutex_ = 0)` – Christoph Oct 27 '10 at 20:46
  • +1 (well to be split with @nategoose) for a very efficient version of scope bound ressource management. Just a bit nit picking: you should have `()` around the evaluation of `lock`, and one should mention that this needs C99 because of the `for`-scope variable. – Jens Gustedt Oct 27 '10 at 21:05
  • 3
    Also, this has some pitfalls concerning flow control. `break`, `continue`, `goto` and `return` may do different than the programmer expects: the first two will just terminate the `for` inside the macro, and not of an enclosing construct. The later two will jump over the `pthread_mutex_unlock`, so the lock will not be released. – Jens Gustedt Oct 27 '10 at 21:18
  • @Jens: Drat! I left out the `()` when I retyped it (trying to make it readable as a comment). Flow control will most definitely do bad things here, which is sad because this is such a clever trick otherwise. – nategoose Oct 27 '10 at 21:34
  • 2
    This version won't let you nest synchronized blocks. To fix that I changed `i_` to `i_##lock` (so `synchronized(some_lock)` expands to `for(pthread_mutex_t *i_some_lock = &some_lock; ...`. As a bonus, `synchronized(some_lock) synchronized(some_lock) {}` will cause a compiler error. – Brendan Long Oct 27 '10 at 21:51
  • Oh, except adding `##lock` makes it not work with struct members :( – Brendan Long Oct 27 '10 at 22:04
  • Ok, so I can make them nestable, but not with magic deadlock checking by using Santiago Lezica's code so each lock is named `_lock_ ## __COUNTER__` – Brendan Long Oct 27 '10 at 22:40
  • 2
    @nategoose, @Brendan, there are ways to make such macros a bit more controllable for `break` etc and to have them properly nested without using extensions like `__COUNTER__`. P99 will provide a general framework for that: http://p99.gforge.inria.fr/p99-html/group__preprocessor__blocks_gaa864200ae44c5666b1c42cfa60836a63.html#gaa864200ae44c5666b1c42cfa60836a63 – Jens Gustedt Oct 28 '10 at 08:36
5

Here's a start, but you may need to tweak it:

#define synchronized(lock, func, args...) do { \
    pthread_mutex_lock(&(lock)); \
    func(##args); \
    pthread_mutex_unlock(&(lock)); \
} while (0)

Use like this (unfortunately, not the Java-like syntax you wanted):

synchronized(x, do_thing, arg1, arg2);
Jonathan
  • 13,354
  • 4
  • 36
  • 32
  • It looks like the `do ... while(0)` aren't necessary, the `{ .. }` alone work (at least with gcc). This still requires a block like `synchronized(x, function)` instead of `synchronized(x){ /* function */ }` though. – Brendan Long Oct 27 '10 at 20:02
2

This was the best I came up with:

#define synchronized(x, things) \
      do { \
           pthread_mutex_t * _lp = &(x); \
           pthread_mutex_lock(_lp);      \
           (things);                     \
           pthread_mutex_unlock(_lp);    \
      } while (0)

...

        synchronized(x,(
                          printf("hey buddy\n"),
                          a += b,
                          printf("bye buddy\n")
                        ));

Note that you have to use the rarely used comma operator and there are restrictions to what code can live within the (not quite java-like) synchronization code list.

nategoose
  • 12,054
  • 27
  • 42
1

Very interesting question!

I looked at the other answers and liked the one using for. I have an improvement, if I may! GCC 4.3 introduces the COUNTER macro, which we can use to generate unique variable names.

#define CONCAT(X, Y) X##__##Y
#define CONCATWRAP(X, Y) CONCAT(X, Y)
#define UNIQUE_COUNTER(prefix) CONCATWRAP(prefix, __COUNTER__)

#define DO_MUTEX(m, counter) char counter; \
for (counter = 1, lock(m); counter == 1; --counter, unlock(m))

#define mutex(m) DO_MUTEX(m, UNIQUE_COUNTER(m))

Using those macros, this code...

mutex(my_mutex) {
    foo();
}

... will expand to...

char my_mutex__0;
for (my_mutex__0 = 1, lock(my_mutex); my_mutex__0 == 1; --my_mutex__0, unlock(m)) {
    foo();
}

With my_mutex__n starting in 0 and generating a new name each time its used! You can use the same technique to create monitor-like bodies of code, with a unique-but-unknown name for the mutex.

salezica
  • 74,081
  • 25
  • 105
  • 166
  • I like the approach, but it doesn't work if I pass a struct member. `synchronized(object->lock) { /*stuff*/ }` expands to `char object->lock__0; /*etc*/`, which causes a problem because lock__0 isn't a struct member. – Brendan Long Oct 27 '10 at 21:42
  • Ahh, true =( well, the modified answer above looks better anyway, and doesn't need counters. Cheers! – salezica Oct 27 '10 at 21:55
  • Just naming the variable `_lock_ ## __COUNTER__` seems to work (and solve a problem in the other implementation). – Brendan Long Oct 27 '10 at 22:40