3
\$\begingroup\$

As we all know, the syntax of allocating memory is a bit clunky in C. The recommended way is:

int *p;
int n=10;
p = malloc(n*sizeof *p);

You can use sizeof (int) instead of sizeof *p but it is bad practice.

I made a solution to this with a macro:

#define ALLOC(p,n) do{ *p=malloc(n*sizeof **p); } while(0)

This get called this way:

int *p;
int n=10;
ALLOC(&p, n);

The reason I want to pass the address of p rather than p, is to offer a bit of protection. When you call a function with foo(x), then foo can never change the value of x. If you're not passing the address, the argument will not change. (Sure there are ways around that too.)

Any comments?

\$\endgroup\$
  • \$\begingroup\$ Please see What to do when someone answers. I have rolled back Rev 3 → 2. \$\endgroup\$ – 200_success Jan 26 at 18:10
  • 3
    \$\begingroup\$ I think the basic problem here is that your opinion of the "clunkiness" of C memory allocation is not shared by many experienced C programmers. (To me, it seems elegantly simple.) So by hiding what you're actually doing in a macro, you just introduce possible confusion. \$\endgroup\$ – jamesqf Jan 27 at 5:18
14
\$\begingroup\$

p = malloc(n*sizeof *p);

This is dangerous if n gets large, because the multiplication could overflow. After the overflow, too little memory has been allocated but code will continue without detecting the error.

This is especially dangerous if n comes from untrusted source, such as some file format or remote user. Then it gives attacker easy way to overwrite parts of memory with exploit code.

The easy safe solution is to use calloc() which will detect the overflow (at least on most common libc implementations). If you really need to use malloc() for some reason, you can check for overflow separately.

\$\endgroup\$
  • \$\begingroup\$ Indeed, this is an often overlooked but very important point. I think that any calloc implementation that does not return a null pointer on overflow would not be conforming. \$\endgroup\$ – jamesdlin Jan 26 at 23:38
  • 1
    \$\begingroup\$ @jamesdlin Yes. Still, one should not ignore that calloc() has to zero the memory too. \$\endgroup\$ – Deduplicator Jan 26 at 23:43
7
\$\begingroup\$

You've taken an expression statement and unnecessarily made a do/while statement out of it. You need parentheses around your macro parameters. You don't need to pass in the pointer you're assigning to as a pointer.

Put all that together and you end up with:

#define ALLOC(p, n)  ((p) = malloc((n) * sizeof *(p)))

This puts fewer restrictions on how it is used, and allows things like

struct monster *AddMonsters(monster *monsters, int which_monster, int how_many)
{
    int difficulty_add = ExtraMonsters();
    return ALLOC(monsters + which_monster, how_many + difficulty_add);
}

Yes, you could use monsters[which_monster], add the extra before using the macro, and put a separate return statement, but all that would be unnecessary restrictions on its use.

\$\endgroup\$
  • 1
    \$\begingroup\$ The do-while is just something I always do with macros. A bit redundant here, but it does not hurt. Parenthesis I forgot. Thanks for that. But I do have a good reason for passing it as a pointer. See my edit. \$\endgroup\$ – Broman Jan 26 at 17:18
  • 5
    \$\begingroup\$ @Broman "Does not hurt"? Sure it does. You're unnecessarily making it illegal to use your macro where an expression is expected, such as with if (ALLOC(...)). \$\endgroup\$ – jamesdlin Jan 26 at 23:24
  • \$\begingroup\$ @jamesdlin That's true. Thanks. \$\endgroup\$ – Broman Jan 27 at 0:08
  • 1
    \$\begingroup\$ I would prefer the do-while, personally. The body technically works as an expression, but it’s still an assignment. Those can be surprising in expression position, especially behind a macro. \$\endgroup\$ – gntskn Jan 27 at 1:44
3
\$\begingroup\$

There are a couple questionable things here, first since ALLOC() is a macro you don't need to pass the address of p. If ALLOC() were a function call then you would want to pass the address of p.

Second, for an array I would probably use calloc() rather than malloc().

\$\endgroup\$
  • 1
    \$\begingroup\$ The reason for passing the address is to make it clear that the argument will be modified. \$\endgroup\$ – Broman Jan 26 at 17:54
0
\$\begingroup\$

First of all, remove the unnecessary parts, which can cause errors and make the code harder to read.

#define ALLOC(p,n) p=malloc(n*sizeof *p)

You can make your code more readable with describing names. After months it will be quite hard to understand even your own code.

#define ALLOC(pointer, size) pointer = malloc(size * sizeof *pointer)

Parenthesizes are important! The following call: ALLOC(pointer, size + 1); would be equal with pointer = malloc(size + 1 * sizeof *pointer);, which clearly is a bug.

#define ALLOC(pointer, size) (pointer) = malloc((size) * sizeof(*pointer))

Use calloc instead of malloc, because of security reasons.

#define ALLOC(pointer, size) (pointer) = calloc((size), sizeof(*pointer))

Lastly, do not use function-like macros, instead a function would be a better choice. https://rules.sonarsource.com/c/tag/preprocessor/RSPEC-960

\$\endgroup\$
  • \$\begingroup\$ "because of security reasons" is very vague, and arguably untrue. It's certainly not enough to justify an obvious pessimisation. \$\endgroup\$ – Toby Speight Feb 4 at 12:48
  • \$\begingroup\$ @jpa explained it quite well, so I did not went into details, but I focused on mentioning different, possible error sources. \$\endgroup\$ – Norbert Incze Feb 4 at 14:38

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.