Tworzenie wskaźnika na stałą – różnica pomiędzy C a C++

0

Witam, mam takie nurtujące mnie pytanie, ponieważ zaskoczyło mnie, iż w C++ nie mogę stworzyć wskaźnika do stałej, jeśli on sam też nie będzie const. Natomiast w C można tak robić.

W C to wygląda tak:

#include <stdio.h>

int main()
{
    const double LICZBA_PI =3.14;
    double *wsk = &LICZBA_PI;
    
    printf("%f", *wsk);
    
    return 0;
}

Natomiast w C++ tak się nie da zrobić, choć dzięki temu dziwnemu tworowi, można mieć taki wskaźnik jak w C

#include <iostream>
 
using namespace std;
 
int main()
{
     const double liczbaPI = 3.14;
     const double *wskDoStalej = &liczbaPI;
 
     double *wskaznik = const_cast<double *>(wskDoStalej);
     cout << *wskaznik << endl;
 
     return 0;
}

Czy ktoś mógłby mi odpowiedzieć czemu w C++ musi być to szczególnie zaznaczone, że to wskaźnik na stałą?

0

To raczej wynika z innych opcji kompilacji i użytego kompilatora. Wytłumaczenie ponżej.

Zobacz co się dzieje przy takim kodzie:

#include <stdio.h>
const double PI = 3.14;

int main() {
    double *pi_ptr = &PI;
    *pi_ptr = 10.0;
    printf("%f", *pi_ptr);
    return 0;
}
$ gcc-7 ptr_const.c -o ptr_const
ptr_const.c: In function 'main':
ptr_const.c:5:22: warning: initialization discards 'const' qualifier from pointer target type [-Wdiscarded-qualifiers]
     double *pi_ptr = &PI;

Zauważ, że jest ostrzeżenie.

A dalej:

$ ./ptr_const
[1]    45649 bus error  ./ptr_const

Ups...

Skompilujmy przez g++

$ g++-7 ptr_const.c -o ptr_const
ptr_const.c: In function 'int main()':
ptr_const.c:5:22: error: invalid conversion from 'const double*' to 'double*' [-fpermissive]
     double *pi_ptr = &PI;

Error. Dodajmy -fpermissive.

$ g++-7 ptr_const.c -o ptr_const -fpermissive
ptr_const.c: In function 'int main()':
ptr_const.c:5:22: warning: invalid conversion from 'const double*' to 'double*' [-fpermissive]
     double *pi_ptr = &PI;

Ups, łyknął ...

Co na to clang?

$ clang++ ptr_const.cpp -o ptr_const 
ptr_const.cpp:5:13: error: cannot initialize a variable of type 'double *' with an rvalue of type 'const double *'
    double *pi_ptr = &PI;

Trzeba było zmienić rozszerzenie, bo się clang++ oburzył. Dodanie -fpermissive nie pomoże. Kompilowanie jako c, a nie cpp już jednak pomoże.

const_cast mówi kompilatorowi "wiem co robię stary, wyluzuj".

0

No wszystko ok ale to pozwala na taką dziwaczną rzecz jak zmienienie wartości const w C++ mimo, że przecież z góry ma być const. Niby informuje stary wiem co robię, a sam łamie konwencję stałej. Nie ogarniam tego.

Wiem, że Java to inna bajka ale jak tam jest finalna to koniec kropka.

Chyba, że jest jakieś rzeczywiście praktyczne zastosowanie :P

1

Na własne ryzyko, bo może się wywalić, jak zmodyfikujesz. Zastosowanie jak najbardziej istnieje - użycie stałych w kodzie, który stałych nie przyjmuje (np. biblioteki), ale jednocześnie nie modyfikuje zawartości.

2
nalik napisał(a):
#include <stdio.h>
const double PI = 3.14;

int main() {
    double *pi_ptr = &PI;
    *pi_ptr = 10.0;

Undefined behavior.

const_cast mówi kompilatorowi "wiem co robię stary, wyluzuj".

Problem w tym, że właśnie chyba nie wiesz co robisz.
const_cast pozwala na zdjęcie (albo dodanie, albo nic nie zrobienie) consta, ale nie oznacza to że możesz sobie bezkarnie zmieniać wartość stałej.
Oznacza tylko tyle, że możesz wskaźnik na const przechować w zmiennej która jest teoretycznie nie-const. Jednak nadal nie powinieneś zmieniać wskazywanej wartości.

3

czemu w C++ musi być to szczególnie zaznaczone, że to wskaźnik na stałą?

Dla bezpieczeństwa. Właśnie po to masz się gimnastykować, żeby przez przypadek czegoś takiego nie zrobić. Równie dobrze możesz pytać czemu literału tekstowego nie możesz przypisać do liczby (przecież możesz z reinterpret_cast!)

0
kq napisał(a):

czemu w C++ musi być to szczególnie zaznaczone, że to wskaźnik na stałą?

Dla bezpieczeństwa. Właśnie po to masz się gimnastykować, żeby przez przypadek czegoś takiego nie zrobić. Równie dobrze możesz pytać czemu literału tekstowego nie możesz przypisać do liczby (przecież możesz z reinterpret_cast!)

Odnoszę wrażenie, że nie zrozumiałeś sensu mojego pytania.

1

$5.2.11/7 Ze standardu c++11, nie sądzę, by się dużo zmieniło w nowszych.

[ Note: Depending on the type of the object, a write operation through the pointer, lvalue or pointer
to data member resulting from a const_cast that casts away a const-qualifier72 may produce undefined
behavior (7.1.6.1). — end note ]

0

Testowałem przypisywanie w Visual Studio i Code Blocks. Jeżeli stała jest deklarowana poza main w obu przypadkach program się zawiesza. Jeśli jest deklarowana w main w Code Blocks udaje się normalnie zmienić jej wartość. W Visual Studio wartość uzyskana po wyłuskaniu wskaznika jest zmodyfikowana ale wyświetlanie stałej PI pokazuje oryginalną wartość. Czy to znaczy ze odkryłem sposób na oszczędzanie pamięci? Bo pod jednym adresem są 2 wartości xd.

4

Nie znam się na formułkach ze standardu, ale w skrócie, gdybając:

  1. w C/C++ stałe są tylko umowne. Te języki pozwalają na dowolną zmianę danych, o ile system operacyjny, kompilator i hardware na to pozwala

  2. taka stała może np. wylądować w ROM albo w chronionym obszarze pamięci. Wtedy jej interpretacja jako nie-const się powiedzie, dopóki nic nie zmieniasz.

  3. Może się też zdarzyć że stała zostanie wyeliminowana - tego nie jestem pewien, ale całkiem możliwe że zamiast miejsca w pamięci będzie zredukowana do wartości przypisanej w odpowiednim miejscu bezpośrednio do rejestru (chyba nie dotyczy double - pewnie zaszłość historyczna z czasów gdy CPU i FPU były oddzielnymi scalakami).

  4. const_cast jest perspektywą na istniejącą wartość usuwającą ograniczenia. Gdzie ta wartość jest przechowywana - na to raczej const_cast nie ma wpływu. Tego typu castowania powinno się unikać, ponieważ zafałszowuje rzeczywistość.

0

I ciągnąc dalej co @vpiotr napisał: może się zdarzyć, że sygnatura funkcji to
int foo (long* bar); z uwagi na jakieś zaszłości historyczne, mimo, że równie dobrze mogłoby być int foo(const long* bar), bo funkcja czyta tylko dane spod danego adresu. Tak czy siak w C++ kompilator nie pozwoli przekazać tam stałych danych. Tu z pomocą przychodzi const_cast. Tyle, że to jest praktycznie jedyne jego zastosowanie.

1

Innym zastosowaniem (teoretycznie zbędnym) jest wyraźne nadanie consta:

	int *p;
	const int *cp = const_cast<const int*>(p);

oraz (już zupełnie zbędne) rzutowanie na ten sam typ:

	const int *cpx2 = const_cast<const int*>(cpx);
	int *px2 = const_cast<int*>(px);

można sobie jednak wyobrazić że mamy dwie funkcje

void foo(const int*);
void foo(int*);

mamy zmienną int* a chcemy wymusić uruchomienie tej pierwszej:

foo(const_cast<const int*>(p));
// to samo co
foo((const int*)p);

drugi zapis jest jednak chyba czytelniejszy.

Rzutowanie na to samo (a raczej fakt, że nie jest błędem) może się przydać w template'ach.

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