LINUX.ORG.RU

C++ overloaded operator+ crash


0

0

Такой код:

#include <stdint.h>
#include <unistd.h>

#include <cstdlib>
#include <cstdio>
#include <cstring>

#include <iostream>
using namespace std;

class BinStr {
public:
	uint8_t *data;
	
	BinStr() {
		this->len = 0;
		this->data = NULL;
	}
	
	BinStr(const void *data, size_t len) {
		this->len = len;
		
		this->data = (uint8_t *)malloc(this->len);
		if (this->data == NULL) throw bad_alloc();
		
		memcpy(this->data, data, this->len);
	}
	
	BinStr(const BinStr &obj) {
		this->len = obj.len;
		
		this->data = (uint8_t *)malloc(this->len);
		if (this->data == NULL) throw bad_alloc();
		
		memcpy(this->data, obj.data, this->len);
	}
	
	~BinStr() {
		free(this->data);
	}
	
	size_t size(void) {
		return this->len;
	}
	
	BinStr &operator+=(const BinStr &right) {
		if (this == &right) {
			size_t old_len = this->len;
			this->len *= 2;
			
			this->data = (uint8_t *)realloc(this->data, this->len);
			if (this->data == NULL) throw bad_alloc();
			
			memcpy(this->data + old_len, this->data, old_len);
			
			return *this;
		} else {
			size_t old_len = this->len;
			this->len += right.len;
			
			this->data = (uint8_t *)realloc(this->data, this->len);
			if (this->data == NULL) throw bad_alloc();
			
			memcpy(this->data + old_len, right.data, right.len);
			
			return *this;
		}
	}
	
	const BinStr operator+(const BinStr &right) {
		//return (BinStr(this->data, this->len) += right);
		//return BinStr(*this) += right;

		BinStr ret = *this;
		ret += right;
		return ret;
	}
private:
	size_t len;
};

int main(int argc, char **argv) {
	BinStr b2("hi\x00""5", 4);
	BinStr b3("tell", 4);
	BinStr b4 = BinStr();
	b4 = b3 + b2;
	write(fileno(stdout), b4.data, b4.size());
	
	return 0;
}

выпадает в такой crash dump:

@°î*** glibc detected *** ./a.out: double free or corruption (fasttop): 0x0000000000eeb070 ***
======= Backtrace: =========
/lib/libc.so.6[0x7f3cf6d986c8]
/lib/libc.so.6(cfree+0x76)[0x7f3cf6d9a1d6]
./a.out[0x40103c]
./a.out(__gxx_personality_v0+0x210)[0x400cb0]
/lib/libc.so.6(__libc_start_main+0xe6)[0x7f3cf6d445c6]
./a.out(__gxx_personality_v0+0x59)[0x400af9]
======= Memory map: ========
00400000-00402000 r-xp 00000000 03:03 524302                             /home/user/projects/icq/a.out
00601000-00602000 r--p 00001000 03:03 524302                             /home/user/projects/icq/a.out
*** и т.д. ***

Беда наступает из-за строчки `b4 = b3 + b2;` в main(), если её убрать или хотя бы заменить на `b3 + b2;`, т.е. без присваивания переменной, crash не происходит.

Насколько я понял из чтения дампа и отладки в дебагере, на каком то объекте destructor вызывается дважды (скорее всего на том который создаётся внутри operator+ т.е. ret).

Внимание вопрос!

Что я делаю не так? Как с этим бороться? Во всех туториалах которые удалось найти в гугле operator+ показывается именно так как у меня, перепробовал множество вариантов (закомментированы), безрезультатно. А может дело то и не в operator+ совсем а в например copy constructor'е.

Вобщем help!!!!



Последнее исправление: par12b12 (всего исправлений: 1)

> BinStr ret = *this;

теперь думай, что будет когда ret выйдет за область видимости и вызовется деструктор

lester ★★★★
()
Ответ на: комментарий от lester

Да я уж понял, теперь вопрос чё делать. В туториалах везде написано так, но там классы без динамической памяти.

par12b12
() автор топика
Ответ на: комментарий от par12b12

у меня в таких случаях разновидность «умного» указателя используется, чтоб не копировать без нужды данные сразу, а только при изменении данных, так же и в Qt сделано

lester ★★★★
()
Ответ на: комментарий от lester

Ну про memcpy я знаю. Но разве memcpy в constructor'е не достаточно? Покажите код пожалуста. Я вобще понял что переменная ret уничтожается при выходе из operator+, а потом ещё раз в main(). Так куда её копировать с помощью memcpy? Задача, насколько я понимаю, заключается в том что бы её destructor не вызывался при выходе из operator+.

par12b12
() автор топика
Ответ на: комментарий от par12b12

Если не хочешь 2.71бать мозг, то используй vector < uint8_t >. А если хочешь, то определяй оператор присваивания и конструктор копирования.

Reset ★★★★★
()
Ответ на: комментарий от namezys

free, malloc, C++, ручная работа с памятью, невладеющие указатели

ужас

Согласен, но что делать. Я бы тоже всю жизнь писал на Ruby, но к сожалению не везде это приемлемо.

par12b12
() автор топика
Ответ на: комментарий от par12b12

а оператор присвоения где?

BinStr ret = *this;

теперь думай, что будет когда ret выйдет за область видимости и вызовется деструктор

это вызов копирующего конструктора

namezys ★★★★
()
Ответ на: комментарий от Reset

Если не хочешь 2.71бать мозг, то используй vector < uint8_t >. А если хочешь, то определяй оператор присваивания и конструктор копирования.

Vector не пойдёт, данные предназначены для передачи в низкоуровневые ф-ции и системные вызовы которые возможно будут даже модифицировать память по указателю.

Конструктор копирования уже есть, а оператор присваивания необходим тут разве? Я, честно говоря, не очень понял что он делает.

par12b12
() автор топика
Ответ на: комментарий от par12b12

> данные предназначены для передачи в низкоуровневые ф-ции и системные вызовы которые возможно будут даже модифицировать память по указателю

что мешает отдать указатель на первый( нулевой ) элемент вектора?

lester ★★★★
()
Ответ на: комментарий от namezys

а оператор присвоения где?

BinStr ret = *this;

теперь думай, что будет когда ret выйдет за область видимости и вызовется деструктор

это вызов копирующего конструктора

Вот именно, только как сделать что бы дестрактор для ret не вызывался при выходе из operator+.

par12b12
() автор топика
Ответ на: комментарий от par12b12

> Vector не пойдёт, данные предназначены для передачи в низкоуровневые ф-ции и системные вызовы которые возможно будут даже модифицировать память по указателю.

vector обратно совместим с T *, поэтому подойдет

Reset ★★★★★
()
Ответ на: комментарий от par12b12

> Вот именно, только как сделать что бы дестрактор для ret не вызывался при выходе из operator+.

не как. ты его создал - он там живет

namezys ★★★★
()
Ответ на: комментарий от namezys

4 строчка в мейне - присвоение

А разве тут нельзя обойтись копирующим конструктором, т.е. здесь необходим operator= ?

что мешает отдать указатель на первый( нулевой ) элемент вектора?

Я наверно перепутал с string.c_str который даёт только const указатель. Однако теперь уже всё равно интересно разобраться, не даст покоя этот баг.))

par12b12
() автор топика
Ответ на: комментарий от par12b12

> А разве тут нельзя обойтись копирующим конструктором, т.е. здесь необходим operator= ?

У тебя что написано? вот оно и вызывается. А конструктор - при создании

namezys ★★★★
()
Ответ на: комментарий от namezys

не как. ты его создал - он там живет

А если я создам там BinStr *ret = new BinStr(this->data, this->len); и потом возвращу из ф-ции return *ret; тогда утечки памяти не будет никакой? при выходе из main() desctructor вызовется?

par12b12
() автор топика
Ответ на: комментарий от par12b12

нет конечно. ты создал указатель и потом теряешь

return *ret; у тебя вызовет конструктор копирования (необязательно, некоторые компиляторы на это забивают иногда, но только при максимальной оптимизации)

namezys ★★★★
()
Ответ на: комментарий от namezys

Добавил copy constructor:

BinStr &operator=(const BinStr &right) {
	if (this != &right) {
		this->len = right.len;
		
		this->data = (uint8_t *)realloc(this->data, this->len);
		if (this->data == NULL) throw bad_alloc();
		
		memcpy(this->data, right.data, this->len);
	}
	return *this;
}

Теперь всё в порядке. Большое спасибо за помощь.

par12b12
() автор топика
Ответ на: комментарий от par12b12

Добавил copy constructor

Опечатка, хотел сказать operator=

par12b12
() автор топика
Ответ на: комментарий от par12b12

> Vector не пойдёт, данные предназначены для передачи в низкоуровневые ф-ции и системные вызовы

А std::vector — всего лишь удобная обёртка над обычным массивом. Так что отдавай указатель на первый его элемент — и всё в порядке. Я в WinAPI-шные функции так отдавал векторы, всё работало.

one_more_hokum ★★★
()
Ответ на: комментарий от one_more_hokum

ну надо понимать некоторые ограничения вектора и все будет хорошо

namezys ★★★★
()

У тебя не определён оператор присваивания, из-за чего используется побайтовое копирование по умолчанию

runtime ★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.