2

If I'm writing 100% ANSI C but compiling in .cpp files will the compiler automatically "optimize" malloc and free calls to new and delete? Does that even make sense given their differences? I didn't think this is how it worked but a friend of mine said this is what happens.

  • 3
    `malloc` doesn't invoke constructors, while `new` does. Hence, "optimizing" something to do more work - doesn't make sense. – Algirdas Preidžius Nov 27 '18 at 15:16
  • 1
    AFAIK no compiler would do this. `malloc` is legal to call in C++ and sometimes is actually what you need to do. – NathanOliver Nov 27 '18 at 15:16
  • I don't see how you would consider `new` an optimized `malloc`. `new` often does more work than `malloc`. – François Andrieux Nov 27 '18 at 15:17
  • 1
    My impression -- this probably isn't 100% accurate, but I think it's useful -- is that it's the other way around: `new` and `delete` are implemented in terms of `malloc` and `free`. – Steve Summit Nov 27 '18 at 15:17
  • 3
    "100% ANSI C" is not 100% compatible with a C++ compiler. For example: `int main(int argc, char **argv) { if (argc == 0) return 0; else return main(0, 0); }` – pmg Nov 27 '18 at 15:18
  • 1
    Often but no necessarily `new` and `delete` end up calling `malloc` and `free` at some point. – Jabberwocky Nov 27 '18 at 15:20
  • @Jabberwocky That sounds like a fishy implementation. I would rather hope the operator doesn't invoke any C libs but call the API heap functions straight away. – Lundin Nov 27 '18 at 15:25
  • I don't think that converting malloc to new was valid optimization, hence putting it in quotes. I also never thought this was what happened when you called malloc in C++ but I wanted to confirm it. – Organoompkin Nov 27 '18 at 15:39

3 Answers3

6

C++ is very specific in c.malloc:

The functions calloc(), malloc(), and realloc() do not attempt to allocate storage by calling ::operator new().

The function free() does not attempt to deallocate storage by calling ::operator delete().

Community
  • 1
  • 1
Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271
  • 7
    Does the standard have requirements about that? This feels like an implementation detail to me and not true of all platforms. – François Andrieux Nov 27 '18 at 15:19
  • 1
    Many functions have intrinsic versions which will not result in an external to be resolved by the linker/loader. `malloc()` is not commonly one of them, but I know of no reason that it couldn't be. – Ben Voigt Nov 27 '18 at 15:21
  • The quote at the end is the correct answer to the question, +1 – Ben Voigt Nov 27 '18 at 15:22
  • 1
    Where does the quote come from? – NathanOliver Nov 27 '18 at 15:23
  • 1
    Calls to `printf` where the argument is a string with no format specifiers that ends in `\n` is often optimized by replacing it with `puts`, but that also has external linkage, does it not? – Govind Parmar Nov 27 '18 at 15:24
  • @NathanOliver From [c.malloc]. – Maxim Egorushkin Nov 27 '18 at 15:28
  • Thanks, Looks good now. I'd up vote but I don't buy the linkage argument. You could just says C++ forbids it which would make it a lot better IMHO. – NathanOliver Nov 27 '18 at 15:30
  • @GovindParmar The compiler only does it if `printf` comes from the standard headers. Do not include standard headers, declare your own `printf` and see what happens. – Maxim Egorushkin Nov 27 '18 at 15:34
  • The fact that `malloc` has external linkage is not relevant to whether it may be optimized out. The C++ standard permits an implementation **only** to emulate *observable behavior* (C++ 2018 4.6 [intro.execution] 1). Because the behavior of `malloc` is specified in the standard, its behavior is known to the implementation, and this enables the implementation to replace calls to it with alternative code that implements the same observable behavior. Clang 9.1.0 in fact does so. – Eric Postpischil Nov 27 '18 at 15:57
  • @EricPostpischil `malloc` is not prohibited to have observable side effects that the compiler knows nothing about. `malloc` is a library function that can have many different implementations, which it has. – Maxim Egorushkin Nov 27 '18 at 15:59
  • @MaximEgorushkin: You may be thinking that a `malloc` implementation could provide extended features, such as for debugging. Yes, it can do so (although I am not sure the result conforms to the standard). However, a C++ implementation is not required by the standard to support these. It may assume `malloc` behaves as specified in the standard. As I wrote, Clang does make this optimization. – Eric Postpischil Nov 27 '18 at 16:03
  • @EricPostpischil Can you provide more information of that Clang optimisation you refer to? – Maxim Egorushkin Nov 27 '18 at 16:04
  • @MaximEgorushkin: With Apple LLVM 9.1.0 clang-902.0.39.2 (sorry, wrong version number earlier), `int foo() { int *x = (int *) malloc(sizeof x); *x = 3; int y = *x; free(x); return y; }` compiles to `movq %rsp, %rbp; movl $3, %eax; popq %rbp; retq`, compiling with `-O3` for x86_64. – Eric Postpischil Nov 27 '18 at 16:06
  • Even if `malloc` were permitted to have side effects, it is part of the C++ implementation. (Some compilers and libraries are shipped or developed separately. Neither is a C++ implementation by itself; only the whole package, including the execution environment, is a C++ implementation.) Therefore, the C++ implementation may have complete knowledge of what `malloc` does and is free to optimize it. A compiler that works with a separate library may or may not require the `malloc` in the library to behave in certain ways, as it chooses. – Eric Postpischil Nov 27 '18 at 16:08
  • @EricPostpischil As I mentioned, the compiler only does that if it can prove you are using `malloc` from the standard headers; and the pointer does not leak outside the function. Very limited local optimisation. – Maxim Egorushkin Nov 27 '18 at 16:12
  • @MaximEgorushkin: If code uses `std::malloc`, it is using `malloc` “from the standard headers.” And, no, optimization is not restricted to use of the pointer within one function. `int *foo() { return (int *) malloc(sizeof(int)); } int bar() { int *x = foo(); *x = 3; int y = *x; free(x); return y; }` optimizes `bar` to the same code I showed earlier. If the compiler is able to produce different code with the same observable behavior, it may do so, regardless of whether function boundaries are crossed. – Eric Postpischil Nov 27 '18 at 16:16
  • @EricPostpischil If `foo` is function with external linkage and the compiler cannot prove it is used only in this translation unit then it won't be able to do this optimization. – Maxim Egorushkin Nov 27 '18 at 16:20
  • @MaximEgorushkin: The compiler both produces an externally visible implementation for `foo` and inlines it into `bar`, then optimizes `bar` as a I stated. The result is that the code path through `bar` calls `malloc` in the C++ abstract machine but does not in the actual assembly code. This conforms to the C++ standard. The compiler **is** allowed to optimize away calls to `malloc`. – Eric Postpischil Nov 27 '18 at 16:23
  • @MaximEgorushkin: Thank you. – Eric Postpischil Nov 27 '18 at 16:25
5

There's a bit of an ambiguity in the question.

int *ip1 = malloc(sizeof int);
int *ip2 = new int;

Those two in fact do the same thing: create an uninitialized value on the heap and assign its address to the pointer on the left-hand side.

But:

struct S { /* whatever */ };
S *sp1 = malloc(sizeof S);
S *sp2 = new S;

Those two don't necessarily do the same thing. If S has a constructor, new S will allocate memory and call the constructor; malloc(sizeof S) will only allocate memory.

I mentioned an ambiguity. There's another possible meaning for "replace new, and that is using calls to operator new:

struct S { /* whatever */ };
S *sp1 = malloc(sizeof S);
S *sp2 = ::operator new(sizeof S);

On the surface, by default these two do the same thing: they allocate memory on the heap for an object of type S and return a pointer to that memory; neither one initializes the object. But there's an important difference. If malloc can't allocate memory it returns a null pointer. If operator new can't allocate memory it throws an exception of type std::bad_alloc (there's more to it than that, but that's enough difference for now).

That's also true for new S: it throws an exception if it can't allocate memory, while malloc returns a null pointer.

Pete Becker
  • 74,985
  • 8
  • 76
  • 165
1

Do C++ compilers generally “optimize” malloc and free to new and delete?

No.

Optimizing is an act that reduces the workload of your program.

Since new and delete invoke constructors and destructors respectively, while malloc() and free() do not, it makes no sense to optimize them.

Usually new will call malloc(), which also adds to my point above, as mentioned in Does ::operator new(size_t) use malloc()?

PS: "I'm writing 100% ANSI C" is not going to make a C++ compiler happy in any way...

gsamaras
  • 71,951
  • 46
  • 188
  • 305
  • 2
    FWIW, there are aren't any constructors in C code so nothing would actually be called. – NathanOliver Nov 27 '18 at 15:20
  • _Optimizing is an act that reduces the workload of your program._ that depends on the optimisation objective and there are at least: optimise for debugging, optimise for speed, optimise for size. – Maxim Egorushkin Nov 27 '18 at 15:41