Function call dengan pointer ke non-const dan pointer ke const const dari alamat yang sama

14

Saya ingin menulis fungsi yang menginput array data dan output array data lain menggunakan pointer.

Saya bertanya-tanya apa hasilnya jika keduanya srcdan dstmenunjuk ke alamat yang sama karena saya tahu kompiler dapat mengoptimalkan untuk const. Apakah itu perilaku yang tidak terdefinisi? (Saya menandai C dan C ++ karena saya tidak yakin apakah jawabannya mungkin berbeda di antara mereka, dan saya ingin tahu tentang keduanya.)

void f(const char *src, char *dst) {
    dst[2] = src[0];
    dst[1] = src[1];
    dst[0] = src[2];
}

int main() {
    char s[] = "123";
    f(s,s);
    printf("%s\n", s);
    return 0;
}

Selain pertanyaan di atas, apakah ini didefinisikan dengan baik jika saya menghapus constkode asli?

Willy
sumber

Jawaban:

17

Meskipun benar bahwa perilaku didefinisikan dengan baik - tidak benar bahwa kompiler dapat "mengoptimalkan konst" dalam arti yang Anda maksud.

Artinya, sebuah kompiler tidak diperbolehkan menganggap bahwa hanya karena parameternya adalah const T* ptr, memori yang ditunjuk oleh ptrtidak akan diubah melalui pointer lain. Pointer bahkan tidak harus sama. Ini constadalah kewajiban, bukan jaminan - kewajiban Anda (= fungsi) untuk tidak melakukan perubahan melalui pointer itu.

Untuk benar-benar memiliki jaminan itu, Anda harus menandai penunjuk dengan restrictkata kunci. Jadi, jika Anda mengkompilasi dua fungsi ini:

int foo(const int* x, int* y) {
    int result = *x;
    (*y)++;
    return result + *x;
}

int bar(const int* x, int* restrict y) {
    int result = *x;
    (*y)++;
    return result + *x;
}

yang foo()fungsi harus membaca dua kali dari x, sementara bar()hanya perlu membaca sekali:

foo:
        mov     eax, DWORD PTR [rdi]
        add     DWORD PTR [rsi], 1
        add     eax, DWORD PTR [rdi]  # second read
        ret
bar:
        mov     eax, DWORD PTR [rdi]
        add     DWORD PTR [rsi], 1
        add     eax, eax              # no second read
        ret

Lihat ini hidup GodBolt.

restricthanya kata kunci dalam C (sejak C99); sayangnya, belum diperkenalkan ke C ++ sejauh ini (untuk alasan yang buruk yang lebih rumit untuk memperkenalkannya di C ++). Namun, banyak kompiler yang mendukungnya __restrict.

Intinya: Kompiler harus mendukung use case "esoterik" Anda saat kompilasi f(), dan tidak akan memiliki masalah dengannya.


Lihat posting ini tentang kasus penggunaan untuk restrict.

einpoklum
sumber
constbukan "kewajiban Anda (= fungsi) untuk tidak melakukan perubahan melalui pointer itu". Standar C memungkinkan fungsi untuk menghapus constmelalui gips dan kemudian memodifikasi objek melalui hasilnya. Pada dasarnya, constini hanyalah saran dan kemudahan bagi programmer untuk membantu menghindari memodifikasi objek secara tidak sengaja.
Eric Postpischil
@EricPostpischil: Ini kewajiban yang bisa Anda dapatkan.
einpoklum
Kewajiban yang bisa Anda dapatkan bukanlah kewajiban.
Eric Postpischil
2
@EricPostpischil: 1. Rambut Anda membelah di sini. 2. Itu tidak benar.
einpoklum
1
Inilah sebabnya memcpydan strcpydideklarasikan dengan restrictargumen, sementara memmovetidak - hanya yang terakhir memungkinkan tumpang tindih antara blok memori.
Barmar
5

Ini didefinisikan dengan baik (dalam C ++, tidak yakin dalam C lagi), dengan dan tanpa constkualifikasi.

Hal pertama yang harus dicari adalah aturan aliasing yang ketat 1 . Jika srcdan dstmenunjuk ke objek yang sama:

Mengenai constkualifikasi, Anda mungkin berpendapat bahwa karena ketika dst == srcfungsi Anda secara efektif mengubah srcpoin apa , srctidak boleh dikualifikasikan sebagai const. Ini bukan cara constkerjanya. Dua kasus perlu dipertimbangkan:

  1. Ketika suatu objek didefinisikan sebagai const, seperti dalam char const data[42];, memodifikasinya (secara langsung atau tidak langsung) mengarah ke Perilaku Tidak Terdefinisi.
  2. Ketika referensi atau penunjuk ke constobjek didefinisikan, seperti dalam char const* pdata = data;, seseorang dapat memodifikasi objek yang mendasarinya asalkan belum didefinisikan sebagai const2 (lihat 1.). Jadi yang berikut ini didefinisikan dengan baik:
int main()
{
    int result = 42;
    int const* presult = &result;
    *const_cast<int*>(presult) = 0;
    return *presult; // 0
}

1) Apa aturan aliasing yang ketat?
2) Apakah const_castaman?

YSC
sumber
Mungkin OP berarti kemungkinan penataan ulang tugas?
Igor R.
char*dan char const*tidak kompatibel. _Generic((char *) 0, const char *: 1, default: 0))mengevaluasi ke nol.
Eric Postpischil
Ungkapan "Ketika referensi atau pointer ke constobjek didefinisikan" salah. Anda berarti bahwa ketika referensi atau penunjuk ke tipe-const kualifikasi didefinisikan, itu tidak berarti objek yang diatur ke titik tidak dapat dimodifikasi (dengan berbagai cara). (Jika pointer menunjuk ke suatu objek, itu berarti objek tersebut memang berdasarkan definisi, sehingga perilaku mencoba memodifikasinya tidak didefinisikan.)constconst
Eric Postpischil
@ Eric, saya hanya spesifik ketika pertanyaannya tentang Standar atau ditandai language-lawyer. Exactitude adalah nilai yang saya hargai, tetapi saya juga sadar itu datang dengan lebih banyak kompleksitas. Di sini, saya memutuskan untuk memilih kalimat yang sederhana dan mudah dipahami, karena saya percaya ini adalah keinginan OP. Jika Anda berpikir sebaliknya, silakan jawab, saya akan menjadi orang pertama yang mengangkatnya. Bagaimanapun, terima kasih atas komentar Anda.
YSC
3

Ini didefinisikan dengan baik dalam C. Aturan aliasing yang ketat tidak berlaku dengan chartipe, atau dengan dua pointer dari tipe yang sama.

Saya tidak yakin apa yang Anda maksud dengan "optimalkan untuk const". Kompiler saya (GCC 8.3.0 x86-64) menghasilkan kode yang sama persis untuk kedua kasus. Jika Anda menambahkan restrictspecifier ke pointer, maka kode yang dihasilkan sedikit lebih baik, tetapi itu tidak akan berfungsi untuk kasus Anda, pointer menjadi sama.

(C11 §6.5 7)

Objek harus memiliki nilai yang disimpan diakses hanya oleh ekspresi lvalue yang memiliki salah satu dari jenis berikut:
- jenis yang kompatibel dengan jenis objek yang efektif,
- versi yang memenuhi syarat dari jenis yang kompatibel dengan jenis objek yang efektif,
- tipe yang tipe bertanda tangan atau tidak bertanda yang sesuai dengan jenis objek yang efektif,
- jenis tipe yang bertanda tangan atau tidak bertanda yang sesuai dengan versi terkualifikasi dari jenis objek yang efektif,
- tipe agregat atau gabungan yang mencakup satu dari jenis-jenis yang disebutkan di atas di antara para anggotanya (termasuk, secara rekursif, anggota dari sub-agregat atau kesatuan yang terkandung), atau
- jenis karakter.

Dalam hal ini (tanpa restrict), Anda akan selalu mendapatkan 121hasilnya.

SS Anne
sumber