1

I'm working on a code I requested from a researching team. I'm trying to understand the code, however, they used malloc in a strange way. here;

in the header file;

 #define ERROR_OUTPUT stderr
 #define FAILIF(b) {   \
   if (b) {  \
       fprintf(ERROR_OUTPUT, "FAILIF triggered on line %d, file %s. Memory allocated: %lld\n",  \
       __LINE__, __FILE__, totalAllocatedMemory); exit(1); \
   } \
  }
 #define MALLOC(amount) \ 
   ( (amount > 0) ? totalAllocatedMemory += amount, malloc(amount) : NULL)

in the cpp file;

 double *memRatiosForNNStructs = NULL;
 double *listOfRadii = NULL;
 nRadii = 1;
 FAILIF(NULL == (memRatiosForNNStructs = (double*)MALLOC(nRadii * sizeof(double))));

From my understanding, the MALLOC that they defined means the following;

if(amount>0) // which is true; at least in this case
{
   totalAllocatedMemory = totalAllocatedMemory + amount; // sounds reasonable
   malloc(amount)  // What?? This will leak memory...
}
else
{
   NULL; // Do nothing? I guess that's fine
}

Is there something that I'm missing here? Or did they just make a (naive) mistake?

osgx
  • 90,338
  • 53
  • 357
  • 513
Roronoa Zoro
  • 1,013
  • 2
  • 15
  • 25

4 Answers4

8

The third code snippet you have is not equivalent. Notice the use of the comma operator:

#define MALLOC(amount) \
    ( (amount > 0) ? totalAllocatedMemory += amount, malloc(amount) : NULL)  
                                                   ^
                                                  N.B.!

The comma operator takes two arguments, evaluates and discards the first expression, then evaluates and returns the second expression.

The ternary conditional operator used this way

a = ((b)?(c:d))

is equivalent to this

if(b) {
    a = c;
}
else {
    a = d;
}

The comma operator used this way

e = f, g;

is equivalent to this

f;
e = g;

So if you have

a = ((b)?(f,g:d))

then that is equivalent to

if(b) {
    f;
    a = g;
}
else {
    a = d;
}

In the code provided in your original post, the MALLOC macro is to be used like this:

memRatiosForNNStructs = (double*)MALLOC(nRadii * sizeof(double));

which is equivalent to this:

   // First operand of ternary conditional
if(nRadii * sizeof(double) > 0)
{
    // Second  operand of ternary conditional

    // First expression of the comma operator
    totalAllocatedMemory += nRadii * sizeof(double));
    // Second expression of the comma operator
    memRatiosForNNStructs = (double*)malloc(nRadii * sizeof(double));
}
else
{
    // Third operand of ternary conditional
    memRatiosForNNStructs = (double*)NULL;
}

Honestly though, this could be achieved as a function in C with no loss of generality:

void* my_malloc(unsigned int amount)
{
    if(amount > 0) {
        // I assume this is a global variable
        totalAllocatedMemory = totalAllocatedMemory + amount;
        return  malloc(amount);
    }
    else {
        return NULL;
    } 
}

memRatiosForNNStructs = (double*)my_malloc(nRadii * sizeof(double));

So I'm not sure what's the point of even implementing it as a hard-to-read macro.

In silico
  • 51,091
  • 10
  • 150
  • 143
  • "Notoriously baneful", I think. – Kerrek SB Feb 06 '12 at 00:36
  • "N.B." means "nota bene", i.e. more or less "watch carefully". – Matteo Italia Feb 06 '12 at 00:36
  • @RoronoaZoro: *Nota bene*—“note well”; “take notice”. – Jon Purdy Feb 06 '12 at 00:37
  • @In silico: Awesome, I think my main confusion was when they used the comma. Thanks for the detailed explanation. You mentioned that this is a hard-to-read-macro. Can you suggest/(give hints on) the best way, just to learn good programming practice. – Roronoa Zoro Feb 06 '12 at 01:01
  • @Roronoa Zoro: From what you've said in your question, the `my_malloc()` function in my answer seems to work just as well as the `MALLOC` macro, but being easier to read and safer to use. – In silico Feb 06 '12 at 01:06
  • @Insilico: Thank you. I'm highly interested in knowing best programming practices. Are long macros bad programming practice and must be replaced with separate functions? Things like that. I'm sure there are many discussions about that, so I won't ask the general questions here, but your answer is complete to me. Thank you – Roronoa Zoro Feb 06 '12 at 01:11
  • 1
    @Roronoa Zoro: Macros aren't always bad practice. [See this Stack Overflow question for situations where macros need to be used](http://stackoverflow.com/questions/96196/when-are-c-macros-beneficial). That being said, you should avoid using macros in lieu of other C++ language features unless absolutely necessary, because of the fact that macros aren't typesafe by themselves, for example. – In silico Feb 06 '12 at 01:17
  • 1
    Sometimes macros are unavoidable as has been pointed out, but in this case they are absolutely avoidable. The problem with macros is that one can't even trace into them aside from scoping issues, type safety issues, etc. It's not fun to debug a large scale program consisting of many macro calls that induce numerous side effects. – stinky472 Feb 06 '12 at 01:32
3

You're dealing with the comma operator, which evaluates each operand in order and returns the return value of the last operand. So a + b, malloc(n) first evaluates a + b, then malloc(n), and then returns the result of the latter. So the return type of the entire ternary conditional expression is void *.

The best approximation to the ternary expression is a function:

void * MALLOC(unsigned int n) {
   if (n > 0) {
      totalAllocatedMemory += n;
      return malloc(n);
   } else {
      return NULL;
   }
}
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • Does it always return the last operand? Even if there were many operands? – Roronoa Zoro Feb 06 '12 at 00:41
  • @RoronoaZoro: I'm pretty sure that it *evaluates* each operand. And the operator is only *binary*. – Kerrek SB Feb 06 '12 at 00:42
  • I'm not sure I understand what you mean by the operator is only binary? – Roronoa Zoro Feb 06 '12 at 00:47
  • 1
    @RoronoaZoro: That was just to clarify that there are only two operands for each invocation of the operator. `a, b, c, d` calls the comma operator three times. – Kerrek SB Feb 06 '12 at 00:48
  • Ok, so you're saying `a, b, c, d` will first execute `a` then become `b, c, d`, then execute `b` and become `c, d`, then finally execute `c` and return `d`. Nice. Makes absolute sense. – Roronoa Zoro Feb 06 '12 at 00:51
1

They are using a confusing combination of the ternary operator and of the comma operator.

Keep in mind that:

  • the ternary operator evaluates its first operand and returns the second if the first is true, otherwise the third;
  • the comma operator evaluates both its operands and returns the second.

Thus, if amount>0, the expression will return totalAllocatedMemory += amount, malloc(amount); since the comma operator executes both expressions but returns only the second, the final return value of the expression is the one from the malloc.

Still, that ugly macro would have been much clearer (and with no performance loss) if it were an inline function.

Matteo Italia
  • 123,740
  • 17
  • 206
  • 299
1

The MALLOC macro is not quite the same as your expansion.

The ternary operator will return the pointer returned by the call to malloc in the case that the condition is true, and a NULL pointer in the case that the condition is false.

As long as you assign the result of the ternary operator to a vairable there's no memeory leak.

Andrew Cooper
  • 32,176
  • 5
  • 81
  • 116