bajt + bajt = int… dlaczego?


365

Patrząc na ten kod C #:

byte x = 1;
byte y = 2;
byte z = x + y; // ERROR: Cannot implicitly convert type 'int' to 'byte'

Wynik dowolnej matematyki wykonanej na typach byte(lub short) jest domyślnie rzutowany z powrotem na liczbę całkowitą. Rozwiązaniem jest jawne przeniesienie wyniku z powrotem do bajtu:

byte z = (byte)(x + y); // this works

Zastanawiam się, dlaczego? Czy to jest architektoniczne? Filozoficzny?

Mamy:

  • int+ int=int
  • long+ long=long
  • float+ float=float
  • double+ double=double

Dlaczego więc nie:

  • byte+ byte=byte
  • short+ short= short?

Trochę tła: wykonuję długą listę obliczeń dla „małych liczb” (tj. <8) i przechowuję wyniki pośrednie w dużej tablicy. Korzystanie z tablicy bajtów (zamiast tablicy int) jest szybsze (z powodu trafień w pamięci podręcznej). Ale rozległe rzutowania bajtów rozłożone w kodzie sprawiają, że jest on o wiele bardziej nieczytelny.



10
Przydałaby się tu nie znajomość standardu przez Erica - to jego znajomość konstrukcji języka; co nie dlaczego Ale tak, odpowiedź Erica byłaby dość ostateczna :)
Jon Skeet

143
Różne rozważania poniżej są rozsądnym przybliżeniem rozważań projektowych. Mówiąc bardziej ogólnie: nie uważam bajtów za „liczby”; Myślę o nich jak o wzorach bitów, które można interpretować jako liczby, znaki, kolory lub cokolwiek innego. Jeśli zamierzasz robić na nich matematykę i traktować je jako liczby, wówczas sensowne jest przeniesienie wyniku do typu danych, który jest częściej interpretowany jako liczba.
Eric Lippert

28
@Eric: To ma sens w przypadku bajtu, ale prawdopodobnie nie ma tak dużego sensu w przypadku krótkiego / ushort.
Jon Skeet

23
@Eric: byte1 | byte2wcale nie traktuje ich jak liczb. To traktuje je dokładnie jako wzory bitów. Rozumiem twój punkt widzenia, ale tak się składa, że ​​za każdym razem, gdy robiłem jakąkolwiek arytmetykę na bajtach w C #, faktycznie traktowałem je jako bity, a nie liczby, a takie zachowanie zawsze przeszkadza.
Roman Starkov

Odpowiedzi:


228

Trzeci wiersz fragmentu kodu:

byte z = x + y;

właściwie znaczy

byte z = (int) x + (int) y;

Tak więc nie ma operacji + na bajtach, bajty są najpierw rzutowane na liczby całkowite, a wynikiem dodania dwóch liczb całkowitych jest (32-bitowa) liczba całkowita.


Próbowałem kodu poniżej, ale nadal nie działa. bajt z = (bajt) x + (bajt) y;
Anonimowy

10
to dlatego, że nie ma operacji + dla bajtów (patrz wyżej). Spróbuj bajt z = (bajt) ((int) x + (int) y)
azheglov

35
To musi być najbardziej poprawna, zwięzła odpowiedź. Między bajtami nie ma żadnego argumentu, więc zamiast wyjaśniać, dlaczego „dodawanie dwóch bajtów” działa, czy nie ( nigdy się nie zdarzyło ), to wyraźnie pokazuje, dlaczego wynikiem jest liczba całkowita , ponieważ jedyną rzeczą, która się wydarzyła, jest dodanie 2 liczb wewnętrznych .
RichardTheKiwi

2
Miałem zawroty głowy, czytając wszystkie pozostałe odpowiedzi (bez obrazy dla pana Jona Skeeta). To jest najprostsza odpowiedź, która poprawnie opisuje to, co dzieje się pod maską. Dzięki!
rayryeng

Oto odpowiedź, którą napisałem gdzie indziej, która zawiera program służący do identyfikacji, kiedy ta automatyczna promocja oparta na kompilatorze intma miejsce: stackoverflow.com/a/43578929/4561887
Gabriel Staples

172

Jeśli chodzi o „dlaczego tak się dzieje”, to dlatego, że nie ma żadnych operatorów zdefiniowanych przez C # dla arytmetyki z bajtem, sbyte, short lub ushort, tak jak mówili inni. Ta odpowiedź dotyczy powodów, dla których nie zdefiniowano tych operatorów.

Wierzę, że to w zasadzie ze względu na wydajność. Procesory mają natywne operacje do wykonywania arytmetyki z 32 bitami bardzo szybko. Automatyczne przekształcenie z wyniku na bajt może być wykonane automatycznie , ale skutkowałoby to karami wydajnościowymi w przypadku, gdy tak naprawdę nie chcesz tego zachowania.

Myślę, że jest to wspomniane w jednym z adnotowanych standardów C #. Szukam ...

EDYCJA: Irytujące, przejrzałem teraz specyfikację ECMA C # 2 z adnotacją, specyfikację MS C # 3 z adnotacją i specyfikację CLI adnotacji, i żadne z nich nie wspomina o tym, o ile widzę. Jestem pewien , że widziałem powód podany powyżej, ale jestem pod wrażeniem, jeśli wiem, gdzie. Przepraszamy, fani referencji :(


14
Przykro mi to mówić, ale nie jest to najlepsza odpowiedź.
VVS

42
Czy zanegowałeś każdą odpowiedź, która nie jest najlepsza? ;)
Jon Skeet

55
(Żeby wyjaśnić, tak naprawdę nie mam ochoty na ciebie. Wygląda na to, że każdy ma swoje własne kryteria głosowania w dół, i to jest w porządku. Głosuję tylko za odpowiedzią, jeśli uważam, że jest to czynnie szkodliwe, a nie po prostu idealne. )
Jon Skeet

21
Używam głosowania jako narzędzia, aby uzyskać „najlepszą” odpowiedź na górę. W rzeczywistości stwierdziłem, że w swojej odpowiedzi niewiele powiedziałeś, co było głównym powodem mojej opinii. Innym powodem może być moje subiektywne odczucie, że twój przedstawiciel daje ci dużą premię, jeśli chodzi o głosowanie i lądujesz na „lepszych” odpowiedziach.
VVS

23
IMO najlepszym sposobem na uzyskanie „najlepszej” odpowiedzi na górę jest głosowanie za nią. Szczerze mówiąc, myślę, że najbardziej pouczającą odpowiedzią jest tutaj komentarz Erica w pytaniu ... ale poza tym, jeśli chodzi o perspektywę projektowania (w przeciwieństwie do perspektywy „tego, co robi kompilator”), nie sądzę, że jest wiele odpowiedź poza „wydajnością”. W szczególności tak naprawdę nie kupuję argumentu „zapobiega przepełnieniu” (17 głosów), ponieważ sugerowałoby to int + int = long.
Jon Skeet

68

Myślałem, że już gdzieś to widziałem. Z tego artykułu The Old New Thing :

Załóżmy, że żyliśmy w świecie fantasy, w którym operacje na „bajcie” skutkowały „bajtem”.

byte b = 32;
byte c = 240;
int i = b + c; // what is i?

W tym świecie fantasy wartość i wyniosłaby 16! Dlaczego? Ponieważ oba operandy operatora + są bajtami, więc suma „b + c” jest obliczana jako bajt, co powoduje 16 z powodu przepełnienia liczb całkowitych. (I, jak zauważyłem wcześniej, przepełnienie liczb całkowitych jest nowym wektorem ataku bezpieczeństwa.)

EDYCJA : Raymond broni zasadniczo podejścia C i C ++ pierwotnie przyjętego. W komentarzach broni faktu, że C # przyjmuje to samo podejście, ze względu na kompatybilność wsteczną języka.


42
W przypadku liczb całkowitych, jeśli je dodamy i przepełnia się, nie jest automatycznie przesyłany jako inny typ danych, ale dlaczego to robić z bajtem?
Ryan

2
Z ints robi się przepełniony. Spróbuj dodać int.MaxValue + 1, otrzymujesz -2147483648 zamiast 2147483648.
David Basarab

8
@ Longhorn213: Tak, tak mówi Ryan: int matematyka może się przepełnić, ale matematyka int nie zwraca długich.
Michael Petrotta

28
Dokładnie. Jeśli ma to być środek bezpieczeństwa, to bardzo źle wdrożony;)
Jon Skeet

5
@Ryan: „leniwy” to dość poważny zarzut w stosunku do projektantów języka C #, za coś tak podstawowego jak prymitywna matematyka. Jeśli chcesz ich o coś oskarżyć, uczyń to „nadmierną kompatybilnością wsteczną z C / C ++”.
Michael Petrotta

58

DO#

ECMA-334 stwierdza, że ​​dodawanie jest zdefiniowane jako legalne tylko dla int + int, uint + uint, long + long i ultrong + ulong (ECMA-334 14.7.4). Jako takie, są to operacje kandydujące do rozważenia w odniesieniu do 14.4.2. Ponieważ istnieją niejawne rzutowania z bajtu na int, uint, long i ultrong, wszystkie elementy funkcji dodawania są odpowiednimi elementami funkcji zgodnie z 14.4.2.1. Musimy znaleźć najlepszą domyślną obsadę według reguł w 14.4.2.3:

Rzutowanie (C1) na int (T1) jest lepsze niż rzutowanie (C2) na uint (T2) lub ulong (T2), ponieważ:

  • Jeśli T1 jest int, a T2 jest uint lub ulong, C1 jest lepszą konwersją.

Rzutowanie (C1) na int (T1) jest lepsze niż rzutowanie (C2) na długi (T2), ponieważ istnieje ukryty rzut z int na długi:

  • Jeśli istnieje niejawna konwersja z T1 do T2 i nie istnieje niejawna konwersja z T2 do T1, C1 jest lepszą konwersją.

Dlatego używana jest funkcja int + int, która zwraca wartość int.

Co jest bardzo długą drogą do stwierdzenia, że ​​jest głęboko schowany w specyfikacji C #.

CLI

Interfejs CLI działa tylko na 6 typach (int32, native int, int64, F, O i &). (ECMA-335 partycja 3 sekcja 1.5)

Bajt (int8) nie jest jednym z tych typów i jest automatycznie konwertowany na int32 przed dodaniem. (ECMA-335 partycja 3 sekcja 1.6)


To, że ECMA określa tylko te konkretne operacje, nie uniemożliwiłoby językowi wdrożenia innych przepisów. VB.NET zezwoli byte3 = byte1 And byte2bez użycia rzutowania, ale bezskutecznie rzuci wyjątek środowiska wykonawczego, jeśli zwróci int1 = byte1 + byte2wartość powyżej 255. Nie wiem, czy którykolwiek język zezwoli byte3 = byte1+byte2i wyrzuci wyjątek, gdy przekroczy 255, ale nie wyrzuci wyjątku, jeśli int1 = byte1+byte2zwróci wartość z zakresu 256–510.
supercat

26

Odpowiedzi wskazujące na nieefektywność dodawania bajtów i obcięcia wyniku z powrotem do bajtu są niepoprawne. Procesory x86 mają instrukcje specjalnie zaprojektowane do działania na liczbach całkowitych w ilościach 8-bitowych.

W rzeczywistości dla procesorów x86 / 64 wykonywanie operacji 32-bitowych lub 16-bitowych jest mniej wydajne niż operacji 64-bitowych lub 8-bitowych ze względu na bajt prefiksu argumentu, który należy zdekodować. Na maszynach 32-bitowych wykonywanie operacji 16-bitowych wiąże się z taką samą karą, ale nadal istnieją dedykowane kody dla operacji 8-bitowych.

Wiele architektur RISC ma podobne rodzime instrukcje wydajne słowo / bajt. Te, które na ogół nie mają wartości przechowywania-i-konwersji-do-podpisanej-o-pewnej długości.

Innymi słowy, ta decyzja musiała być oparta na percepcji typu bajtu, a nie z powodu leżącej u podstaw nieefektywności sprzętu.


+1; gdyby tylko ta percepcja nie była błędna za każdym razem, gdy kiedykolwiek się zmieniłem, LUB OR dwa bajty w C # ...
Roman Starkov

Obcinanie wyniku nie powinno wiązać się z żadnymi kosztami wydajności. W asemblerze x86 jest to tylko różnica między kopiowaniem jednego bajtu z rejestru lub czterech bajtów z rejestru.
Jonathan Allen,

1
@JonathanAllen Dokładnie. Jedyną różnicą jest, o ironio, przy przeprowadzaniu konwersji rozszerzającej . Obecny projekt karę wydajności do wykonania instrukcji rozszerzający (albo podpisali rozszerzyć lub niepodpisanych przedłużyć.)
reirab

percepcja typu bajtu ” - może to wyjaśniać to zachowanie dla byte(i char), ale nie dla shortktórego semantycznie jest wyraźnie liczbą.
smls,

13

Pamiętam, jak raz przeczytałem coś od Jona Skeeta (nie mogę tego teraz znaleźć, będę dalej szukać) o tym, jak bajt nie przeciąża operatora +. W rzeczywistości, podczas dodawania dwóch bajtów jak w twojej próbce, każdy bajt jest w rzeczywistości domyślnie konwertowany na liczbę całkowitą. Wynikiem tego jest oczywiście int. Teraz, DLACZEGO to zostało zaprojektowane w ten sposób, poczekam, aż sam Jon Skeet opublikuje :)

EDYCJA: Znaleziono! Świetne informacje na ten temat tutaj .


9

Wynika to z przelania i przenoszenia.

Jeśli dodasz dwie liczby 8-bitowe, mogą one przelać się na 9-ty bit.

Przykład:

  1111 1111
+ 0000 0001
-----------
1 0000 0000

Nie wiem na pewno, ale zakładam, że ints, longsidoubles mają więcej miejsca, ponieważ są dość duże. Są to także wielokrotności 4, które są bardziej wydajne w obsłudze komputerów, ponieważ szerokość wewnętrznej szyny danych wynosi 4 bajty lub 32 bity (64 bity stają się coraz bardziej powszechne). Bajt i skrót są nieco bardziej nieefektywne, ale mogą zaoszczędzić miejsce.


23
Ale większe typy danych nie zachowują się tak samo.
Inisheer

12
Problemy związane z przepełnieniem są na bok. Jeśli weźmiesz swoją logikę i zastosujesz ją do języka, wówczas wszystkie typy danych zwrócą większy typ danych po dodaniu arytmetyki, co z całą pewnością NIE jest prawdą. int + int = int, long + long = long. Myślę, że pytanie dotyczy niespójności.
Joseph

To była moja pierwsza myśl, ale dlaczego nie int + int = long? Więc nie kupuję argumentu o „możliwym przepełnieniu” ... jeszcze <grin>.
Robert Cartaino

11
Aha, a jeśli chodzi o argument „możliwe przepełnienie”, dlaczego nie bajt + bajt = krótki?
Robert Cartaino

A) Dlaczego to działa tak, jak działa, biorąc pod uwagę zasady C #? Zobacz moją odpowiedź poniżej. B) Dlaczego został tak zaprojektowany? Prawdopodobnie tylko względy użyteczności, oparte na subiektywnych osądach dotyczących sposobu, w jaki większość ludzi używa liczb całkowitych i bajtów.
mqp

5

Ze specyfikacji języka C # 1.6.7.5 7.2.6.2 Binarne promocje numeryczne konwertuje oba operandy na int, jeśli nie można go dopasować do kilku innych kategorii. Domyślam się, że nie przeciążyli operatora +, aby przyjąć bajt jako parametr, ale chcą, aby działał nieco normalnie, więc po prostu używają typu danych int.

Język C # Spec


4

Podejrzewam, że C # w rzeczywistości wywołuje operator+zdefiniowane int(co zwraca „ intchyba, że ​​jesteś w checkedbloku”) i pośrednio rzutuje oba twoje bytes/ shortsna ints. Dlatego zachowanie wydaje się niespójne.


3
Pcha oba bajty na stosie, a następnie wywołuje polecenie „dodaj”. W IL dodaj „zjada” dwie wartości i zastąp je int.
Jonathan Allen,

3

Prawdopodobnie była to praktyczna decyzja projektantów języków. W końcu int to Int32, 32-bitowa liczba całkowita ze znakiem. Ilekroć wykonujesz operację na liczbach całkowitych na typie mniejszym niż int, i tak zostanie ona przekonwertowana na 32-bitowy znak int przez większość dowolnego 32-bitowego procesora. To, w połączeniu z prawdopodobieństwem przepełnienia małych liczb całkowitych, prawdopodobnie przypieczętowało umowę. Pozwala to uniknąć ciągłego sprawdzania przekroczenia / niedopełnienia, a gdy końcowy wynik wyrażenia w bajtach będzie w zakresie, pomimo faktu, że na pewnym etapie pośrednim będzie poza zakresem, otrzymasz poprawną wynik.

Inna myśl: należałoby zasymulować przekroczenie / niedopełnienie tych typów, ponieważ nie wystąpiłoby to naturalnie na najbardziej prawdopodobnych docelowych procesorach. Po co się męczyć?


2

Jest to w przeważającej części moja odpowiedź na ten temat, postawiona najpierw na podobne pytanie tutaj .

Wszystkie operacje z liczbami całkowitymi mniejszymi niż Int32 są domyślnie zaokrąglane w górę do 32 bitów przed obliczeniem. Powodem, dla którego wynikiem jest Int32, jest po prostu pozostawienie go takim, jakim jest po obliczeniach. Jeśli zaznaczysz arytmetyczne kody MSIL, jedynym integralnym typem numerycznym, z którym działają, są Int32 i Int64. To „z założenia”.

Jeśli chcesz uzyskać wynik w formacie Int16, nie ma znaczenia, czy wykonasz rzutowanie kodu, czy kompilator (hipotetycznie) wyśle ​​konwersję „pod maską”.

Na przykład, aby wykonać arytmetykę Int16:

short a = 2, b = 3;

short c = (short) (a + b);

Dwie liczby rozszerzyłyby się do 32 bitów, zostaną dodane, a następnie skrócone z powrotem do 16 bitów, tak właśnie planowało to MS.

Zaletą korzystania z krótkich (lub bajtów) jest przede wszystkim przechowywanie w przypadkach, gdy masz ogromne ilości danych (dane graficzne, przesyłanie strumieniowe itp.)


1

Dodawanie nie jest zdefiniowane dla bajtów. Więc są dodawane do int dla dodania. Dotyczy to większości operacji matematycznych i bajtów. (zwróć uwagę, że tak było kiedyś w starszych językach, zakładam, że tak jest dzisiaj).


0

Myślę, że jest to decyzja projektowa o tym, która operacja była bardziej powszechna ... Jeśli bajt + bajt = bajt, być może znacznie więcej osób będzie się przejmować koniecznością rzutowania na int, gdy wynik jest wymagany.


2
Raz mi przeszkadzało w drugą stronę :) Zawsze wydaje mi się, że potrzebuję wyniku bajtu, więc zawsze muszę przesyłać.
Roman Starkov

Tyle że nie musisz rzutować na int. Obsada jest niejawna. Tylko inny sposób jest wyraźny.
Niki,

1
@nikie Myślę, że nie zrozumiałeś mojej odpowiedzi. Jeśli dodanie dwóch bajtów wytworzy bajt, aby zapobiec przepełnieniu, ktoś będzie musiał rzucić operandy (nie wynik) na int przed dodaniem.
fortran

0

Z kodu .NET Framework:

// bytes
private static object AddByte(byte Left, byte Right)
{
    short num = (short) (Left + Right);
    if (num > 0xff)
    {
        return num;
    }
    return (byte) num;
}

// shorts (int16)
private static object AddInt16(short Left, short Right)
{
    int num = Left + Right;
    if ((num <= 0x7fff) && (num >= -32768))
    {
        return (short) num;
    }
    return num;
}

Uprość dzięki .NET 3.5 i nowszym:

public static class Extensions 
{
    public static byte Add(this byte a, byte b)
    {
        return (byte)(a + b);
    }
}

teraz możesz zrobić:

byte a = 1, b = 2, c;
c = a.Add(b);


0

Testowałem wydajność między bajtem a liczbą całkowitą.
Z wartościami int:

class Program
{
    private int a,b,c,d,e,f;

    public Program()
    {
        a = 1;
        b = 2;
        c = (a + b);
        d = (a - b);
        e = (b / a);
        f = (c * b);
    }

    static void Main(string[] args)
    {
        int max = 10000000;
        DateTime start = DateTime.Now;
        Program[] tab = new Program[max];

        for (int i = 0; i < max; i++)
        {
            tab[i] = new Program();
        }
        DateTime stop = DateTime.Now;

        Debug.WriteLine(stop.Subtract(start).TotalSeconds);
    }
}

Z wartościami bajtów:

class Program
{
    private byte a,b,c,d,e,f;

    public Program()
    {
        a = 1;
        b = 2;
        c = (byte)(a + b);
        d = (byte)(a - b);
        e = (byte)(b / a);
        f = (byte)(c * b);
    }

    static void Main(string[] args)
    {
        int max = 10000000;
        DateTime start = DateTime.Now;
        Program[] tab = new Program[max];

        for (int i = 0; i < max; i++)
        {
            tab[i] = new Program();
        }
        DateTime stop = DateTime.Now;

        Debug.WriteLine(stop.Subtract(start).TotalSeconds);
    }
}

Oto wynik:
bajt: 3,57s 157mo, 3,71s 171mo, 3,74s 168mo z procesorem ~ = 30%
int: 4,05s 298mo, 3,92s 278mo, 4,28 294mo z procesorem ~ = 27%
Wniosek:
bajt używa więcej procesora, ale kosztuje mniej pamięci i jest szybszy (być może dlatego, że do przydzielenia jest mniej bajtów)


-1

Oprócz wszystkich innych świetnych komentarzy, pomyślałem, że dodam jeszcze jeden mały smakołyk. Wiele komentarzy zastanawiało się, dlaczego int, long i właściwie jakikolwiek inny typ liczbowy również nie przestrzega tej zasady ... zwraca „większy” typ w odpowiedzi na arytmatykę.

Wiele odpowiedzi miało związek z wydajnością (cóż, 32 bity są szybsze niż 8 bitów). W rzeczywistości liczba 8-bitowa jest nadal liczbą 32-bitową w procesorze 32-bitowym .... nawet jeśli dodasz dwa bajty, część danych, na których działa procesor, będzie 32-bitowa niezależnie od tego ... więc dodanie ints nie będzie być „szybszym” niż dodawanie dwóch bajtów ... to wszystko to samo dla procesora. TERAZ dodanie dwóch liczb wewnętrznych BĘDZIE szybsze niż dodanie dwóch długich sekwencji na 32-bitowym procesorze, ponieważ dodanie dwóch długich wymaga więcej mikroprocesorów, ponieważ pracujesz z liczbami szerszymi niż słowo procesorów.

Myślę, że podstawowy powód spowodowania, że ​​arytmetyka bajtów powoduje ints, jest dość jasny i prosty: 8 bitów po prostu nie idzie bardzo daleko! : D Przy 8 bitach masz niepodpisany zakres 0-255. To nie jest dużo miejsca do pracy ... prawdopodobieństwo, że napotkasz ograniczenia bajtów, jest BARDZO wysokie, gdy używasz ich w arytmetyce. Jednak szansa, że ​​zabraknie Ci bitów podczas pracy z intami, długimi, podwójnymi itp. Jest znacznie niższa ... wystarczająco niska, że ​​bardzo rzadko spotykamy się z potrzebą więcej.

Automatyczna konwersja z bajtu na int jest logiczna, ponieważ skala bajtu jest tak mała. Automatyczna konwersja z int na long, float na double itp. Nie jest logiczna, ponieważ liczby te mają znaczną skalę.


To wciąż nie wyjaśnia, dlaczego byte - bytepowraca intani dlaczego nie rzuca na short...
KthProg

Dlaczego chcesz, aby dodawanie zwracało inny typ niż odejmowanie? Jeśli byte + bytezwraca int, ponieważ 255 + cokolwiek jest większe niż bajt może pomieścić, nie ma sensu, aby jakikolwiek bajt minus jakikolwiek inny bajt zwracał coś innego niż int z punktu widzenia spójności typu zwracanego.
jrista

Nie zrobiłbym tego, to po prostu pokazuje, że powyższy powód jest prawdopodobnie niewłaściwy. Gdyby miało to związek z „dopasowaniem” do wyniku, byteodejmowanie zwróciłoby a byte, a dodanie bajtu zwróciłoby a short( byte+ bytezawsze będzie pasować do a short). Gdyby chodziło o spójność, jak mówisz, to shortnadal wystarczyłoby dla obu operacji, a nie int. Oczywiście jest wiele przyczyn, nie wszystkie z nich muszą być dobrze przemyślane. Lub podany poniżej powód wydajności może być dokładniejszy.
KthProg,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.