Why realloc change data

30 views
Skip to first unread message

Gilles Boisson

unread,
Nov 11, 2019, 3:15:29 AM11/11/19
to emscripten-discuss
Hello, 

I was playing around malloc / realloc to create dynamic buffer accessible from JS, and I realized that realloc change my data inside when the returned pointer change .
I'm not a libc expert but i thought realloc doesn't affect data itself. 

I did a proxy method for debugging and validate the fact that the realloc change data.

EMSCRIPTEN_KEEPALIVE void* _realloc(void* ptr, size_t size){
  unsigned int* val = ptr;

  printf("before %i %i %i %i %i %i \n", val[0], val[1], val[2], val[2], val[4], val[5]);
  void* newPtr = realloc(ptr,size);
  printf("after %i %i %i %i %i %i \n", val[0], val[1], val[2], val[2], val[4], val[5]);
  return newPtr;
}


Here is my makefile

BUILD = emcc
LINK = emcc

SRC_DIR = src
BUILD_DIR = dist
OBJ_DIR = obj

LINK_FLAGS = -O3 -s STANDALONE_WASM -s NO_EXIT_RUNTIME=1  -s EXTRA_EXPORTED_RUNTIME_METHODS='["ccall","cwrap"]' -s EXPORTED_FUNCTIONS='["_malloc","_free"]'
BUILD_FLAGS = -c -O3

PROJECT_NAME = em_app


SOURCES = $(wildcard $(addprefix $(SRC_DIR)/,*.c)) $(wildcard $(addprefix $(SRC_DIR)/glmatrix/,*.c))
OBJECTS = $(SOURCES:$(SRC_DIR)/%.c=$(OBJ_DIR)/%.o)


all: $(PROJECT_NAME)




$(PROJECT_NAME): $(OBJECTS) 
$(LINK) $(OBJECTS) -o $(addprefix $(BUILD_DIR)/,$@.js) $(LINK_FLAGS)


$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c
$(BUILD) $< -o $@ $(BUILD_FLAGS)

clean:
rm -f $(addprefix $(BUILD_DIR)/,$(PROJECT_NAME).js) $(addprefix $(BUILD_DIR)/,$(PROJECT_NAME).wasm) $(OBJECTS)


Any chance to have your lights on the subject.
 
Cheers,

Floh

unread,
Nov 11, 2019, 6:26:48 AM11/11/19
to emscripten-discuss
Your test program doesn't reassign 'val' to the new pointer returned by realloc(), so in the second printf() you're actually reading from the old (not the reallocated) memory location.

If you change the function like this it should work as expected:

EMSCRIPTEN_KEEPALIVE void* _realloc(void* ptr, size_t size){
  unsigned int* val = ptr;

  printf("before %i %i %i %i %i %i \n", val[0], val[1], val[2], val[2], val[4], val[5]);
  void* newPtr = realloc(ptr,size);
  val = newPtr;
  printf("after %i %i %i %i %i %i \n", val[0], val[1], val[2], val[2], val[4], val[5]);
  return newPtr;
}

Reason's why this code may appear to work on native platforms could be:

- realloc() is allowed to grow the memory "inplace" and return the same pointer, but this behaviour isn't guaranteed, one should always assume that a new pointer is returned
- or realloc() might actually return a new pointer, but you're reading from freed memory, and this only has the same content because nobody else has overwritten it yet (this sort of bug can be caught with memory debugging tools like clang address sanitizer, Valgrind or Dr.Memory.

Gilles Boisson

unread,
Nov 11, 2019, 1:49:24 PM11/11/19
to emscripten-discuss
Thanks for your feedback but I did it intentionnaly in order to illustrate that original data (from old pointer) change when we reallocate. My undestanding was that realloc doesn't change the original data and if pointer change you have to copy old data to the new pointer.

Here is the typescript side
const newPtr = (<EmscriptenModuleExtended>this._module)._realloc(
      ptr,
      newLength * Uint32Array.BYTES_PER_ELEMENT
    );
    const newPtrBuffer = new Uint32Array(
      this._module.HEAP8.buffer,
      newPtr,
      newLength
    );

    if (newPtr !== ptr) {
      const length = this.metas[1];
      if (copyBufferIfPtrChanged) {
        //console.log('ptr changed',this._ptrBuffer);
         newPtrBuffer.set(
           newLength < length ? this._buffer.slice(0, length) : this._buffer
         );
      }

      console.log('newPtrBuffer : ', newPtrBuffer);
      this.metas[2] = newPtr;
    }
this.metas[1] = newLength;
    this._buffer = newPtrBuffer;


Floh

unread,
Nov 11, 2019, 2:07:42 PM11/11/19
to emscripten-discuss

> My undestanding was that realloc doesn't change the original data and if pointer change you have to copy old data to the new pointer.

That's not correct, see here:


Specifically:

b) allocating a new memory block of size new_size bytes, copying memory area with size equal the lesser of the new and the old sizes, and freeing the old block.

Since (a) (inplace expansion/shrinking) is not guaranteed, one should always assume that the location has changed. But in either case, the calling code doesn't need to copy the data over, this will always happen inside realloc() as long as the realloc has succeeded.

Cheers!

Gilles Boisson

unread,
Nov 11, 2019, 2:10:29 PM11/11/19
to emscripten-discuss
Ah thanks, 

That's normal, I don't need to copy the buffer myself, realloc does it for me, that's why data changed.

Thanks for your reply.
Reply all
Reply to author
Forward
0 new messages