Funkcja zwracający wskaźnik - błędy

0

witam,
mam takie zadanko z kolokwium polegające na wypisaniu błędów będących w ciele funkcji. Co do jednej rzeczy nie jestem pewien i chciałbym się upewnić, czy dobrze myślę. Kod:

 #include "stdio.h"

int *fun(int *, int *, int, int);

int *fun(int *v1, int *v2, int v1size, int v2size)
{
    if(v1size != v2size)
        return NULL; // blad
    const int size = v1size;
    int v[size];
    for(int i = 0; i < size; i++)
        v[i] = v1[i] + v2[i];
    printf("%d\n", v);
    return v;
}

int main(int argc, char **argv)
{
    int v1size = 4, v2size = 4;
    int v1[4] = {[0] = 3};
    int v2[4] = {[0] = 1};
    int x = *fun(v1, v2, v1size, v2size);
    printf("%d\n", x);
    return 0;
}

Wyobrażam sobie to tak:
return v zwraca adres do pierwszego elementu tablicy, która jest zmienną lokalną. Potem następuje wyjście z funkcji i odczytanie wartości spod zwracanego adresu - ale zmienna lokalna zakończyła już swój żywot, więc pod tym adresem może teraz być cokolwiek. Więc return v to błąd.
Dobrze myślę?

3

Yep, zwracanie adresu zmiennej lokalnej to błąd.

2

Można zwrócić obszar statyczny lub globalny lub przydzielony za pomocą malloc().

3

To jest undefined behavior (format jest niepoprawny, 7.21.6/9):

printf("%d\n", v);

to też (6.2.4/7, 6.2.4/2):

int x = *fun(v1, v2, v1size, v2size);

W tym drugim jest tak jak mówisz, następuje dereferencja wskaźnika do zmiennej, która nie istnieje.

Dodatkowymi niepoprawnym rzeczami w tym kodzie są:

  • Używanie int do określania rozmiaru tablic. Do tego jest size_t.
  • Postinkrementacja tam gdzie zwyczajowo powinna być preinkrementacja.
  • Nieużywane zmienne (argc, argv)
0

No i się dowiedziałem paru ciekawych rzeczy. W takim razie miałbym jeszcze parę pytań w związku z odpowiedzią Endrju.

Zdziwiłem się, że wyświetlanie adresu w formacie dziesiętnym jest UB. Doczytałem więc trochę o tym i proszę powiedzcie mi, czy dobre wnioski wyciągnąłem:

  • Aby wyświetlić adres zmiennej należy to zrobić z rzutowaniem adresu na typ "void *" i wyświetlić go w formie heksadecymalnej, np.
int x;
printf("%p", (void*)&x);

Pytanie - czy można wyświetlić adres w formie oktadecymalnej, tj. z użyciem specyfikatora "%o"?

Z kolei jak chcemy robić arytmetykę wskaźników, to należy użyć typu "ptrdiff_t".
Ok, to załóżmy, że chcę zobaczyć jaka jest różnica w bajtach pomiędzy 2 elementami tabeli w pamięci (tu swoją drogą kolejne pytanie - czy standard wymusza alokowanie elementów obok siebie w pamięci, jeśli jest to możliwe? Czy jest tak samo w C++? bo właściwie zawsze jak patrzę na adresy elementów z tablicy, to wychodzi na to, że tak). Należałoby wtedy zrobić to w ten sposób?

#include <stdio.h>
#include <stddef.h>

int main(void)
{
    const size_t N = 100;
    int numbers[N];
    int *p2, *p1;
    p1 = &numbers[0];
    p2 = &numbers[10];  // 10 el. roznicy, czyli 40 bajtow
    ptrdiff_t diff = p2-p1;
    printf("Roznica pomiedzy p1 a p2 w bajtach: %td\n", sizeof(int) * diff);
    return 0;
}

Jeśli dobrze kojarzę, to arytmetyka na typie niepełnym jest illegal, tj. nie można tego zrobić w ten sposób:

printf("Roznica pomiedzy p1 a p2 w bajtach: %ld\n", (void*)p2-(void*)p1);

Prawda?
Aczkolwiek kompiluje się bez problemu i daje dobry wynik (xcode 7.3).

I kończąc temat z "size_t" i "ptrdiff_t" - podejrzałem sobie po wykonaniu preprocesora, że to jest po prostu "typedef unsigned long int" i "signed long int" odpowiednia dla tych typów - czyli jak rozumiem te typy są stworzone po prostu dla wygody programisty i niejako informowania o tym, że zmienna tego typu będzie mówiła o ilość elementów np. w tablicy / będzie wykonywana operacja na wskaźnikach?

Kolejna sprawa, nie mogłem znaleźć odniesień w dokumentacji do punktów wymienionych przez Endrju (jedyna darmowa dokumentacja C99 jaką znalazłem to http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf) i nie wiem co tu jest źle (pomijając przypisywanie niewiadomej wartości spod niezaalokowanego adresu)

 int x = *fun(v1, v2, v1size, v2size);
3

W printf jest UB ponieważ format jest niepoprawny. v jest typu int *, który nie jest dozwolony dla formatu %d. Dla takiego typu należy użyć albo %p albo zrzutować wskaźnik na liczbę typu uintptr_t i użyć formatu PRIuPTR (lub PRIoPTR/PRIxPTR aby uzyskać liczbę w systemie ósemkowym/szesnastkowym). Np tak:

printf("pointer oct: " PRIoPTR "\n", (uintptr_t)v);

http://ideone.com/AH99ke

Elementy tablicy muszą być umieszczone w ciągłym obszarze pamięci (6.2.5/20). Taki kod: (void*)p2-(void*)p1 w C nie powinien się kompilować (6.5.6/3, 6.2.5/19). Dla wskaźników, operator - wymaga, żeby oba operandy były wskaźnikami do complete type a void taki nie jest.

Te typy (size_t, uintptr_t, ptrdiff_t) powstały dla zwiększenia czytelności oraz dlatego, że implementacje mogą się różnić jeżeli chodzi o to, jakie typy kryją się pod tymi aliasami. Grunt, że w danej implementacji zawsze będzie to odpowiedni typ dla danego zadania.

W tamtym kodzie z funkcją jest źle dokładnie to co napisałeś w swoim poście - robiona jest dereferencja wskaźnika do zmiennej, która nie istnieje.

Wszystkie numerki ze standardu pochodzą z C11 (n1570).

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