Optimalisasi strlen yang tidak terduga saat menggunakan array 2-d

28

Ini kode saya:

#include <string.h>
#include <stdio.h>

typedef char BUF[8];

typedef struct
{
    BUF b[23];
} S;

S s;

int main()
{
    int n;

    memcpy(&s, "1234567812345678", 17);

    n = strlen((char *)&s.b) / sizeof(BUF);
    printf("%d\n", n);

    n = strlen((char *)&s) / sizeof(BUF);
    printf("%d\n", n);
}

Menggunakan gcc 8.3.0 atau 8.2.1 dengan level optimasi apa pun kecuali -O0, ini akan keluar 0 2ketika saya mengharapkan 2 2. Kompilator memutuskan bahwa strlenterikat b[0]dan karenanya tidak pernah sama atau melebihi nilai yang dibagi.

Apakah ini bug di kode saya atau bug di compiler?

Ini tidak dijabarkan dalam standar dengan jelas, tetapi saya pikir interpretasi arus utama dari sumber pointer adalah bahwa untuk objek apa pun X, kode tersebut (char *)&Xharus menghasilkan pointer yang dapat beralih ke seluruh X- konsep ini harus berlaku bahkan jika Xkebetulan memiliki sub-array sebagai struktur internal.

(Pertanyaan bonus, apakah ada tanda gcc untuk mematikan pengoptimalan khusus ini?)

MM
sumber
4
Ref: Laporan gcc 7.4.0 saya di 2 2bawah berbagai opsi.
chux - Reinstate Monica
2
@Ale jaminan standar mereka berada di alamat yang sama (struct tidak dapat memiliki padding awal)
MM
3
@ DavidRankin-ReinstateMonica "menghasilkan batas-batas char (*) [8] terbatas pada b [0]. Tapi itu sejauh yang saya dapatkan" Saya pikir itu berhasil. karena s.bterbatas untuk b[0]itu terbatas pada 8 karakter, dan karenanya dua opsi: (1) akses keluar jika ada 8 karakter non-nol, yaitu UB, (2) ada karakter nol, di mana len kurang dari 8, maka membaginya dengan 8 memberi nol. Jadi menyusun (1) + (2) kompiler dapat menggunakan UB untuk memberikan hasil yang sama untuk kedua kasus
user2162550
3
Mengingat & s == & s.b, hasilnya tidak mungkin berbeda. Seperti yang ditunjukkan @ user2162550, strlen () tidak dipanggil dan kompiler menebak apa hasilnya, bahkan dalam case godbolt.org/z/dMcrdy di mana kompiler tidak dapat mengetahuinya. Ini adalah bug penyusun .
Ale

Jawaban:

-1

Ada beberapa masalah yang bisa saya lihat dan mereka dapat dipengaruhi oleh bagaimana kompiler memutuskan untuk tata letak memori.

    n = strlen((char *)&s.b) / sizeof(BUF);
    printf("%d\n", n);

Dalam kode di atas s.badalah array entri 23 array 8 karakter. Ketika Anda merujuk hanya s.bAnda mendapatkan alamat entri pertama dalam array 23 byte (dan byte pertama dalam array 8 karakter). Ketika kode mengatakan &s.b, ini meminta alamat dari array. Di bawah penutup, kompiler kemungkinan besar menghasilkan beberapa penyimpanan lokal, menyimpan alamat array di sana dan memasok alamat penyimpanan lokal ke strlen.

Anda memiliki 2 solusi yang memungkinkan. Mereka:

    n = strlen((char *)s.b) / sizeof(BUF);
    printf("%d\n", n);

atau

    n = strlen((char *)&s.b[0]) / sizeof(BUF);
    printf("%d\n", n);

Saya juga mencoba untuk menjalankan program Anda dan menunjukkan masalah, tetapi baik dentang dan versi gcc yang saya miliki dengan -Oopsi apa pun masih berfungsi seperti yang Anda harapkan. Untuk apa nilainya, saya menjalankan dentang versi 9.0.0-2 dan gcc versi 9.2.1 di x86_64-pc-linux-gnu).

JonBelanger
sumber
-2

Ada kesalahan dalam kode.

 memcpy(&s, "1234567812345678", 17);

misalnya, berisiko, meskipun s dimulai dengan b harus:

 memcpy(&s.b, "1234567812345678", 17);

Strlen kedua () juga memiliki kesalahan

n = strlen((char *)&s) / sizeof(BUF);

misalnya, harus:

n = strlen((char *)&s.b) / sizeof(BUF);

String sb, jika disalin dengan benar, harus sepanjang 17 huruf. Tidak yakin bagaimana struct disimpan dalam memori, jika disejajarkan. Sudahkah Anda memeriksa bahwa sb sebenarnya mengandung 17 karakter yang disalin?

Jadi strlen (sb) harus menunjukkan 17

Printf hanya menunjukkan angka integer, karena% d adalah integer, dan variabel n dinyatakan sebagai integer. sizeof (BUF), harus 8

Jadi 17 dibagi 8 (17/8) harus mencetak 2 karena n dinyatakan sebagai bilangan bulat. Karena memcpy digunakan untuk menyalin data ke s dan bukan ke sb, saya kira karena ini berkaitan dengan penyelarasan memori; dengan asumsi itu adalah komputer 64 bit, maka dapat ada 8 karakter pada satu alamat memori.

Misalnya, mari kita asumsikan bahwa seseorang telah memanggil malloc (1), daripada "ruang kosong" berikutnya tidak selaras ...

Panggilan strlen kedua, menunjukkan nomor yang benar, ketika salinan string dilakukan ke s struct bukan ke sb

pengguna413990
sumber