Prawdopodobnie problem z operatorem przypisania – tylko czemu?

0

Hej, program kompiluje się, jednak w momencie użycia operatora przypisania program przestaje działać jak należy (nie pokazuje dalszych coutów). Ktoś może wie dlaczego :)?

Plik nagłówkowy

#ifndef COW_H_INCLUDED
#define COW_H_INCLUDED

class Cow
{
private:
  char cowName[20];
  char * cowHobby;
  double cowWeight;

public:
    Cow();
    Cow(const char * name, const char * hobby, double weight);
    Cow(const Cow &c);
    ~Cow();
    Cow & operator=(const Cow &c);
    void showCow() const;
};

#endif // COW_H_INCLUDED

Funckje

#include <iostream>
#include "cow.h"
#include <cstring>

using namespace std;

Cow::Cow()
{
    cowName[0] = '\0';
    cowHobby = nullptr;
    cowWeight = 0;

    cout << "Krowa wyimaginowana powstala" << endl;
}

Cow::Cow(const char * name, const char * hobby, double weight)
{
    strncpy(cowName, name, 20);
    int chSize = strlen(hobby);
    cowHobby = new char[chSize + 1];
    strncpy(cowHobby, hobby, 20);
    if (weight > 0)
        cowWeight = weight;
    else
        cowWeight = 0;
        cout << "Krowa z konstruktora powstala" << endl;
}

Cow::Cow(const Cow &c)
{
    strcpy(cowName, c.cowName);
    if (c.cowHobby == nullptr)
        cowHobby = nullptr;
    else
        strcpy(cowHobby, c.cowHobby);
    cowWeight = c.cowWeight;
    cout << "Uzyto konstruktora kopiujacego" << endl;
}

Cow::~Cow()
{
    cout << "Zabilem byka, coz to byl za byk! Krew z niego sika - siku sik sik. Imie jego to: " << cowName;
    delete [] cowHobby;
}

Cow & Cow::operator=(const Cow &c)
{
    if (this == &c)
        return *this;
    delete [] cowHobby;
    int chSize = strlen(c.cowHobby);
    cowHobby = new char[chSize + 1];
    strcpy(cowName, c.cowName);
    strcpy(cowHobby, c.cowHobby);
    cowWeight = c.cowWeight;
    cout << "Uzyto operatora przypisania" << endl;
    return *this;
}

void Cow::showCow() const
{
    cout << endl;
    cout << "cowName: " << cowName << endl;
    cout << "cowHobby: " << cowHobby << endl;
    cout << "cowWeight: " << cowWeight << endl << endl;
}

Main

#include <iostream>
#include "cow.h"

using namespace std;

int main()
{
    Cow K1;
    cout << "K1" << endl;
    Cow K2("K2", "Jesc", 99);
    cout << "K2" << endl;
    Cow K3 = K2;
    cout << "K3" << endl;
    Cow K4 = Cow(K2);
    cout << "K4" << endl;
    Cow K5(K2);
    cout << "K5" << endl;
    Cow * ptr = new Cow;
    *ptr = K2;
    cout << "ptr" << endl;

    K1.showCow();
    K2.showCow();
    K3.showCow();
    K4.showCow();
    K5.showCow();
    ptr->showCow();

    return 0;
}

2

Korzystaj z narzędzi, w tym przypadku kompilacja z użyciem Address Sanitizera (-fsanitize=address) pokazuje:

[pts/1:krzaq@ArchVM:~/code/temp]% ./a.out
Krowa wyimaginowana powstala
K1
=================================================================
==2569==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000000015 at pc 0x7f4fbe27e79f bp 0x7ffe8c79c150 sp 0x7ffe8c79b8f8
WRITE of size 20 at 0x602000000015 thread T0
    #0 0x7f4fbe27e79e in __interceptor_strncpy /build/gcc/src/gcc/libsanitizer/asan/asan_interceptors.cc:436
    #1 0x564c5489a425 in Cow::Cow(char const*, char const*, double) /home/krzaq/code/temp/test.cpp:40
    #2 0x564c5489aaee in main /home/krzaq/code/temp/test.cpp:94
    #3 0x7f4fbdd60222 in __libc_start_main (/usr/lib/libc.so.6+0x24222)
    #4 0x564c5489a1ed in _start (/home/krzaq/code/temp/a.out+0x21ed)

0x602000000015 is located 0 bytes to the right of 5-byte region [0x602000000010,0x602000000015)
allocated by thread T0 here:
    #0 0x7f4fbe321f19 in operator new[](unsigned long) /build/gcc/src/gcc/libsanitizer/asan/asan_new_delete.cc:93
    #1 0x564c5489a3d9 in Cow::Cow(char const*, char const*, double) /home/krzaq/code/temp/test.cpp:39
    #2 0x564c5489aaee in main /home/krzaq/code/temp/test.cpp:94
    #3 0x7f4fbdd60222 in __libc_start_main (/usr/lib/libc.so.6+0x24222)

SUMMARY: AddressSanitizer: heap-buffer-overflow /build/gcc/src/gcc/libsanitizer/asan/asan_interceptors.cc:436 in __interceptor_strncpy
Shadow bytes around the buggy address:
  0x0c047fff7fb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c047fff7fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c047fff7fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c047fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c047fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0c047fff8000: fa fa[05]fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c047fff8010: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c047fff8020: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c047fff8030: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c047fff8040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c047fff8050: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==2569==ABORTING
// Cow::Cow(char const*, char const*, double) /home/krzaq/code/temp/test.cpp:40
Cow::Cow(const char * name, const char * hobby, double weight)
{
    strncpy(cowName, name, 20);
    int chSize = strlen(hobby);
    cowHobby = new char[chSize + 1];
    strncpy(cowHobby, hobby, 20); // linia 40
    if (weight > 0)
        cowWeight = weight;
    else
        cowWeight = 0;
        cout << "Krowa z konstruktora powstala" << endl;
}

Alokujesz chSize+1, a zawsze zapisujesz 20 znaków.

Ponadto, dodatkowa nauka:
https://dsp.krzaq.cc/post/176/ucze-sie-cxx-kiedy-uzywac-new-i-delete/
https://en.cppreference.com/w/cpp/language/rule_of_three

Czyli używaj std::string, std::vector i innych dobrodziejstw biblioteki standardowej.

1

strcpy musi mieć gdzie kopiować, chyba konstruktor kopiujący się wywala nie operator przypisania

2
Cow::Cow(const Cow &c)
{
    strcpy(cowName, c.cowName);
    if (c.cowHobby == nullptr)
        cowHobby = nullptr;
    else
        strcpy(cowHobby, c.cowHobby);

W tym miejscu cowHobby jest niezainicjowane, a próbujesz tam coś pisać.

jednak w momencie użycia operatora przypisania program przestaje działać jak należy (nie pokazuje dalszych coutów)

W kodzie masz UB (undefined behavior), niezdefiniowane zachowanie. Wynik takiego programu może być dowolny, nie można polegać na tym który cout ci pokazało a którego nie. Nawet pierwszy (Krowa wyimaginowana powstala) teoretycznie może się nie wyświetlić.

0

Dzięki wielkie wszystkichm za pomoc. Już się biorę za ogarnianie.

@kq tak jeszcze podpytam. Zadanko wykonywałem na podstawie książki (Prata) dlatego chary, no i new i delete. Ogólnie już kiedyś wspominałeś mi o std::vector natomiast idąc z książką cały czas męczone jest new/delete -_- Jak to wygląda w praktyce, gdy np. pracujemy już na jakimś starszym kodzie? New/delete jest wgl używane i warto używać do zadań go by ogarnąć czy raczej wszędzie jest usuwane i podmieniane? To samo z charami - biblioteka string jest dużo wygodniejsza tylko pytanie czy też nie warto jednak w ramach ćwiczeń dalej z tego korzystać?

0

Prata zbiera średnie recenzje, pewnie m. in. z tego powodu.

W nowym kodzie, jeśli korzystasz z new/delete i nie potrafisz tego uzasadnić benchmarkami/koniecznością (co dzieje się bardzo rzadko), to kod zawierający new/delete nie powinien przechodzić code review.

1 użytkowników online, w tym zalogowanych: 0, gości: 1