Jaka jest różnica między tą pętlą a tym wywołaniem memcpy

0

Piszę sobie skrypt w C. Mam problem w miejscu, w którym kopiuję tablicę wskaźników.

Napisałem pętlę do tego kopiowania, ale wydało mi się, że jest to sposób, który generuje więcej błędów od pojedynczego wywołania funkcji. Zakomentowałem więc pętlę i wywołałem funkcję memcpy z odpowiednimi argumentami. Ale okazało się, że działa gorzej (?) niż pętla. PS. A w każdym razie – inaczej.

Patrzę w ten kod już któryś raz od wczoraj i wydaje mi się, że oba fragmenty powinny działać tak samo. No kurczę, dokładnie tak samo.

Chciałbym prosić o porównanie, czy kod pętli i funkcji jest taki sam. To znaczy, czy rezultat powinien być taki sam niezależnie od tego, co będą przechowywać użyte zmienne (byle przechowywały to samo dla obu przypadków).

Nie chodzi mi o sprawdzenie poprawności wyniku obu tych fragmentów; rozumiem, że do tego potrzeba by całego kodu. Ma kilka funkcji i dużo miejsc, w których są sprawdzane błędy, więc myślę, że nie ma sensu go pokazywać. Stoję na stanowisku, że nikomu nie chciałoby się go czytać, a co dopiero debugować.

Tutaj deklaracje zmiennych używanych w obu fragmentach:

char **words = (coś tam);
size_t *actualTokenWordsNumber = (coś tam);
char **tokenWords = (coś tam);

Tutaj wersja głównej pętli z wykorzystaną pętlą:

for (size_t i = 0, k = 0; i < (*actualTokensNumber); ++i)
{
    (tu trochę się dzieje)

    for (size_t m = 0; m < (*actualTokenWordsNumber); ++m)
    {
        words[k++] = tokenWords[m];
    }
}

A tutaj wersja głównej pętli z wykorzystanym wywołaniem memcpy:

size_t actualWordsNumber = 0;
for (size_t i = 0; i < (*actualTokensNumber); ++i)
{
    (tu trochę się dzieje)

    actualWordsNumber += (*actualTokenWordsNumber);
    memcpy(
        words + ((actualWordsNumber - 1) * sizeof(char *)),
        tokenWords,
        (*actualTokenWordsNumber) * sizeof(char *));
}

UPDATE: Dodałem informację o tym, że oba te fragmenty są w pętli, ponieważ ma to znaczenie dla fragmentu z pętlą (wartość zmiennej k).


UPDATE2: Dodałem fragment kodu, który ma znaczenie dla wartości zmiennej actualWordsNumber.


UPDATE3: Przeniosłem deklarację zmiennej actualWordsNumber we właściwe miejsce i ją dokończyłem, bo zauważyłem, że ma to znaczenie dla wersji z pętlą.

0

Wersja z pętlą kopiuje w words od początku po kolei,
Natomiast memcpy - użyte absolutnie niepoprawnie, ma być:

for (size_t i=0,k=0;i<(*actualTokensNumber);++i)
{
    (tu trochę się dzieje)

    memcpy(words+k,tokenWords,(*actualTokenWordsNumber)*sizeof(char*));
    k+=(*actualTokenWordsNumber);
}
1
Silv napisał(a):

Piszę sobie skrypt w C. Mam problem w miejscu, w którym kopiuję tablicę wskaźników.
...
Chciałbym prosić o porównanie, czy kod pętli i funkcji jest taki sam.

Polecam: https://godbolt.org/

0

@_13th_Dragon: dlaczego Twoja wersja wywołania memcpy nie używa *sizeof(char *)? Przecież pierwszy argument to początek pamięci przechowującej tablicę wskaźników, a każdy z nich musi mieć swój rozmiar.

A nawet jeśli założyć, że ten rozmiar jest równy zawsze 1, to i tak porównując oceniam, że Twoja wersja jest identyczna w działaniu z moją.

1

@Silv: brakuje Ci tego sizeofa u Smoka gdzie, w pierwszym argumencie? Jeśli tak to błędnie go tam używasz, dodanie liczby do wskaźnika przesuwa o wielokrotność typu wskaznikowego a nie bajt.

1

Jeśli używasz gcc to popatrz na źródła i porównaj https://github.com/gcc-mirror/gcc/blob/master/libgcc/memcpy.c

0

Np. dla tego kodu:

#include <iostream>
#include <cstdint>
#include <cstring>
 
 void show(char tab[]) {
    for (int i = 0; i < 4; i++) {
        std::cout << tab[i] << '\n';
    }
 }

int main()
{
    // simple usage
    char source[] = "once upon a midnight dreary...", dest[4];
    std::memcpy(dest, source, sizeof dest);
    show(dest);
}

memcpy rozwinięte jest do dwóch "mównięć":

mov     eax, DWORD PTR [rbp-32]
mov     DWORD PTR [rbp-36], eax

https://godbolt.org/z/qLNMNb

0

@alagner: dzięki! Naprowadziłeś mnie na oczywisty błąd. To znaczy tak: może cały problem w wątku to z mojej strony problem XY, ale tego już stwierdzić sam nie mogę. W każdym razie wydaje się, że kod zmienił się na lepsze. Na pewno program zachowuje się inaczej po usunięciu tej różnicy. W zasadzie nie powinienem był akceptować posta, bo jednak wyjścia programu nie są jednakowe. Podobne, ale nie jednakowe. Może znajdziesz więcej różnic… jeśli nie, to zaakceptuję z powrotem (bo, skoro nie dałem kodu całego programu, to głupio byłoby wymagać debugowania jego wyjścia).

@_13th_Dragon: dzięki. Niestety, zanim spróbowałem zrozumieć drugi Twój post, to @alagner już mnie naprowadził na różnicę, którą Ty zaznaczyłeś.

@UczonyHumanista: jeśli dobrze rozumiem, że próbowałeś mnie naprowadzić na arytmetykę wskaźników, to bardzo trudno byłoby mi to zauważyć, gdyby @alagner wcześniej nie dał wskazówki.

@vpiotr: jeśli nie znam asemblera, to chyba niewiele mi pomoże. ;)


PS. Krótko mówiąc kod w C prosty nie jest. Może w przypływie bezradności wkleję cały kod, byście pomogli mi znaleźć wszystkie błędy. Na razie wciąż mam nadzieję, że naprawa tego fragmentu z kopiowaniem posunie mnie do przodu.

0

Piszę oddzielny post, bo semantycznie jest oddzielny. Rozwiązałem, zdaje się, problem różnic. Tak mi się wydaje; wyjście jest takie samo w obu przypadkach, a sprawdzać wyjścia "print debugging" nie będę.

  1. Pierwsza różnica jest w samej pętli. Zauważył to @_13th_Dragon, a wspomniał o tym @UczonyHumanista, a @alagner przedstawił to w sposób, w jaki zrozumiałem. Chodzi o to, że dodając coś do wskaźnika, nie musimy już mnożyć tego czegoś przez liczbę bajtów – arytmetyka wskaźników "zrobi to" za nas. Tak więc ten kod:

    words + ((actualWordsNumber - 1) * sizeof(char *)),
    

    powinien stać się tym:

    words + actualWordsNumber - 1,
    
  2. Druga różnica jest poza pętlą. Zauważyłem ją ja, dodając, mnożąc, odejmując i czego tam jeszcze arytmetyka nie ma do zaoferowania – nawet dzieląc. Chodzi o to, że dodawałem liczbę dodanych elementów zawsze przed wywołaniem memcpy. A powinienem po nim. Przez to kod dodawał elementy jakby dwukrotnie; dokładnie nie sprawdzałem – nie uznałem, że warto. W każdym razie teraz jest logicznie. Tak więc ten kod:

    actualWordsNumber += (*actualTokenWordsNumber); // przed "memcpy" – źle!
    memcpy(
        words + actualWordsNumber - 1,
        tokenWords,
        (*actualTokenWordsNumber) * sizeof(char *));
    

    powinien stać się tym:

    memcpy(
        words + actualWordsNumber - 1,
        tokenWords,
        (*actualTokenWordsNumber) * sizeof(char *));
    actualWordsNumber += (*actualTokenWordsNumber); // po "memcpy" – dobrze
    

Gdyby ktoś zauważył, że znów coś źle zrobiłem, albo że jeszcze czegoś nie zauważyłem – pisać!


UPDATE:

  1. Zapomniałem o opisaniu trzeciej różnicy (rzeczywisty kod ją uwzględnia). @_13th_Dragon, @alagner: dziękuję za sprawdzenie. Jest to odejmowanie 1 w memcpy; oczywiście niepoprawne. Teoretycznie powinienem odejmować *actualTokenWordsNumber – gdyby nie uwzględnić różnicy nr 2. Jeśli jednak ją uwzględnić i przenieść wspomnianą linijkę za wywołanie memcpy, to odejmowanie jest już błędne. Tak więc ten kod:

    words + actualWordsNumber - 1
    

    powinien stać się tym:

    words + actualWordsNumber
    

Usunąłem także niepoprawne mnożenie przez sizeof(char *). Napisałem, że jest błędne, ale zapomniałem je skasować ze skopiowanego kodu.


UPDATE2: Tak więc oba fragmenty będą takie same, gdy będą wyglądać tak:

for (size_t i = 0, k = 0; i < (*actualTokensNumber); ++i)
{
    (tu trochę się dzieje)

    for (size_t m = 0; m < (*actualTokenWordsNumber); ++m)
    {
        words[k++] = tokenWords[m];
    }
}
size_t actualWordsNumber = 0;
for (size_t i = 0; i < (*actualTokensNumber); ++i)
{
    (tu trochę się dzieje)

    memcpy(
        words + actualWordsNumber,
        tokenWords,
        (*actualTokenWordsNumber) * sizeof(char *));
    actualWordsNumber += (*actualTokenWordsNumber);
}
0

Tak, znowu, źle. Nadal mnożysz przez sizeof(char *) i nadal nie wiedzieć czemu odejmujesz 1

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