Apakah ada kerugian untuk melewati struct dengan nilai dalam C, daripada melewati pointer?
Jika struct besar, jelas ada aspek performansi menyalin banyak data, tetapi untuk struct lebih kecil, pada dasarnya harus sama dengan melewatkan beberapa nilai ke suatu fungsi.
Itu mungkin bahkan lebih menarik ketika digunakan sebagai nilai pengembalian. C hanya memiliki nilai pengembalian tunggal dari fungsi, tetapi Anda sering membutuhkan beberapa. Jadi solusi sederhana adalah dengan meletakkannya di struct dan mengembalikannya.
Apakah ada alasan untuk atau menentang ini?
Karena mungkin tidak jelas bagi semua orang apa yang saya bicarakan di sini, saya akan memberikan contoh sederhana.
Jika Anda memprogram dalam C, cepat atau lambat Anda akan mulai menulis fungsi yang terlihat seperti ini:
void examine_data(const char *ptr, size_t len)
{
...
}
char *p = ...;
size_t l = ...;
examine_data(p, l);
Ini bukan masalah. Satu-satunya masalah adalah bahwa Anda harus setuju dengan rekan kerja Anda di mana urutan parameter harus jadi Anda menggunakan konvensi yang sama di semua fungsi.
Tetapi apa yang terjadi ketika Anda ingin mengembalikan informasi yang sama? Anda biasanya mendapatkan sesuatu seperti ini:
char *get_data(size_t *len);
{
...
*len = ...datalen...;
return ...data...;
}
size_t len;
char *p = get_data(&len);
Ini berfungsi dengan baik, tetapi jauh lebih bermasalah. Nilai kembali adalah nilai balik, kecuali bahwa dalam implementasi ini tidak. Tidak ada cara untuk mengatakan dari atas bahwa fungsi get_data tidak diizinkan untuk melihat apa yang ditunjukkan oleh len. Dan tidak ada yang membuat kompiler memeriksa apakah suatu nilai benar-benar dikembalikan melalui pointer itu. Jadi bulan depan, ketika orang lain memodifikasi kode tanpa memahaminya dengan benar (karena dia tidak membaca dokumentasi?) Itu rusak tanpa ada yang memperhatikan, atau mulai crash secara acak.
Jadi, solusi yang saya usulkan adalah struct sederhana
struct blob { char *ptr; size_t len; }
Contoh-contoh dapat ditulis ulang seperti ini:
void examine_data(const struct blob data)
{
... use data.tr and data.len ...
}
struct blob = { .ptr = ..., .len = ... };
examine_data(blob);
struct blob get_data(void);
{
...
return (struct blob){ .ptr = ...data..., .len = ...len... };
}
struct blob data = get_data();
Untuk beberapa alasan, saya pikir sebagian besar orang secara instingtif akan membuat exam_data mengambil pointer ke blob struct, tapi saya tidak mengerti mengapa. Itu masih mendapat pointer dan integer, itu hanya jauh lebih jelas bahwa mereka pergi bersama. Dan dalam kasus get_data tidak mungkin untuk mengacaukan dengan cara yang saya jelaskan sebelumnya, karena tidak ada nilai input untuk panjangnya, dan harus ada panjang yang dikembalikan.
sumber
void examine data(const struct blob)
itu tidak benar.gettimeofday
) menggunakan pointer, dan orang-orang mengambilnya sebagai contoh.Jawaban:
Untuk struct kecil (misalnya point, rect) yang melewati nilai sangat bisa diterima. Namun, terlepas dari kecepatan, ada satu alasan lain mengapa Anda harus berhati-hati melewati / mengembalikan struct besar berdasarkan nilai: Stack space.
Banyak pemrograman C adalah untuk sistem tertanam, di mana memori adalah pada premium, dan ukuran tumpukan dapat diukur dalam KB atau bahkan Bytes ... Jika Anda melewati atau mengembalikan struct dengan nilai, salinan struct tersebut akan ditempatkan pada tumpukan, berpotensi menyebabkan situasi bahwa situs ini dinamai setelah ...
Jika saya melihat aplikasi yang tampaknya memiliki penggunaan tumpukan berlebihan, struct yang diteruskan oleh nilai adalah salah satu hal yang saya cari terlebih dahulu.
sumber
Salah satu alasan untuk tidak melakukan ini yang belum disebutkan adalah bahwa ini dapat menyebabkan masalah kompatibilitas biner.
Bergantung pada kompiler yang digunakan, struktur dapat dilewatkan melalui tumpukan atau register tergantung pada opsi / implementasi kompiler
Lihat: http://gcc.gnu.org/onlinedocs/gcc/Code-Gen-Options.html
Jika dua penyusun tidak setuju, hal-hal dapat meledak. Tak perlu dikatakan alasan utama untuk tidak melakukan ini diilustrasikan adalah konsumsi tumpukan dan alasan kinerja.
sumber
int &bar() { int f; int &j(f); return j;};
Untuk benar-benar menjawab pertanyaan ini, orang perlu menggali lebih dalam ke tanah pertemuan:
(Contoh berikut menggunakan gcc pada x86_64. Siapa pun boleh menambahkan arsitektur lain seperti MSVC, ARM, dll.)
Mari kita punya contoh program kami:
Kompilasi dengan optimisasi penuh
Lihatlah majelis:
Inilah yang kami dapatkan:
Tidak termasuk
nopl
bantalan,give_two_doubles()
memiliki 27 byte sementaragive_point()
memiliki 29 byte. Di sisi lain,give_point()
menghasilkan satu instruksi lebih sedikit daripadagive_two_doubles()
Yang menarik adalah kami memperhatikan bahwa kompiler telah dapat mengoptimalkan
mov
ke varian SSE2 yang lebih cepatmovapd
danmovsd
. Lebih jauh lagi,give_two_doubles()
sebenarnya memindahkan data masuk dan keluar dari memori, yang membuat segalanya menjadi lambat.Tampaknya banyak dari ini mungkin tidak berlaku di lingkungan tertanam (yang merupakan tempat bermain untuk C sebagian besar waktu saat ini). Saya bukan penyihir perakitan sehingga komentar apa pun akan diterima!
sumber
const
sepanjang waktu) dan saya menemukan tidak ada banyak penalti kinerja (jika tidak mendapatkan) dalam penyalinan nilai pass-by-value , bertentangan dengan apa yang banyak orang percaya.Solusi sederhana akan mengembalikan kode kesalahan sebagai nilai kembali dan segala sesuatu lainnya sebagai parameter dalam fungsi,
Parameter ini tentu saja merupakan struct, tetapi tidak melihat keuntungan tertentu yang melewati nilai ini, hanya mengirim pointer.
Melewati struktur berdasarkan nilai berbahaya, Anda harus sangat berhati-hati apa yang Anda lewati, ingat tidak ada copy constructor di C, jika salah satu parameter struktur adalah pointer, nilai pointer akan disalin, mungkin akan sangat membingungkan dan sulit untuk mempertahankan.
Hanya untuk menyelesaikan jawaban (kredit penuh untuk Roddy ) penggunaan stack adalah alasan lain tidak lulus struktur berdasarkan nilai, percayalah men-debug stack overflow adalah PITA nyata.
Putar ulang untuk berkomentar:
Melewati struct oleh pointer yang berarti bahwa beberapa entitas memiliki kepemilikan pada objek ini dan memiliki pengetahuan penuh tentang apa dan kapan harus dirilis. Melewati struct dengan nilai membuat referensi tersembunyi ke data internal struct (pointer ke struktur lain dll.) Di ini sulit untuk dipertahankan (mungkin tapi mengapa?).
sumber
Satu hal yang orang-orang di sini lupa sebutkan sejauh ini (atau saya mengabaikannya) adalah bahwa struct biasanya memiliki padding!
Setiap char adalah 1 byte, setiap short adalah 2 byte. Seberapa besar struct? Tidak, ini bukan 6 byte. Setidaknya tidak pada sistem yang lebih umum digunakan. Pada kebanyakan sistem akan menjadi 8. Masalahnya adalah, pelurusan tidak konstan, tergantung pada sistem, sehingga struct yang sama akan memiliki perataan yang berbeda dan ukuran yang berbeda pada sistem yang berbeda.
Tidak hanya itu padding akan semakin memakan tumpukan Anda, itu juga menambahkan ketidakpastian tidak dapat memprediksi padding di muka, kecuali jika Anda tahu bagaimana sistem Anda bantalan dan kemudian melihat setiap struct yang Anda miliki di aplikasi Anda dan menghitung ukurannya. untuk itu. Melewati sebuah pointer membutuhkan ruang yang dapat diprediksi - tidak ada ketidakpastian. Ukuran pointer dikenal untuk sistem, selalu sama, terlepas dari apa yang terlihat seperti struct dan ukuran pointer selalu dipilih dengan cara yang disejajarkan dan tidak perlu bantalan.
sumber
Saya pikir pertanyaan Anda telah merangkum semuanya dengan cukup baik.
Satu keuntungan lain dari melewatkan struct oleh nilai adalah bahwa kepemilikan memori eksplisit. Tidak ada yang bertanya-tanya tentang apakah struct berasal dari heap, dan siapa yang memiliki tanggung jawab untuk membebaskannya.
sumber
Saya akan mengatakan melewati (tidak terlalu besar) struct dengan nilai, baik sebagai parameter dan sebagai nilai pengembalian, adalah teknik yang sangat sah. Seseorang harus berhati-hati, tentu saja, bahwa struct adalah jenis POD, atau semantik salinan ditentukan dengan baik.
Pembaruan: Maaf, saya memakai topi berpikir C ++. Saya ingat saat ketika tidak sah dalam C untuk mengembalikan struct dari suatu fungsi, tetapi ini mungkin telah berubah sejak saat itu. Saya masih akan mengatakan itu valid selama semua kompiler yang Anda harapkan mendukung praktik ini.
sumber
Ini adalah sesuatu yang tidak disebutkan siapa pun:
Anggota dari
const struct
yangconst
, tapi kalau itu anggota adalah pointer (sepertichar *
), menjadichar *const
daripadaconst char *
kita benar-benar inginkan. Tentu saja, kita dapat berasumsi bahwaconst
ini adalah dokumentasi dari niat, dan bahwa siapa pun yang melanggar ini sedang menulis kode yang buruk, tetapi itu tidak cukup baik bagi sebagian orang (terutama mereka yang hanya menghabiskan waktu empat jam mencari penyebab jatuh).Alternatifnya mungkin untuk membuat
struct const_blob { const char *c; size_t l }
dan menggunakannya, tapi itu agak berantakan - itu masuk ke masalah skema penamaan yang sama yang saya miliki dengantypedef
ing pointer. Dengan demikian, kebanyakan orang tetap hanya memiliki dua parameter (atau, lebih mungkin untuk kasus ini, menggunakan pustaka string).sumber
struct const_blob
solusinya adalah bahwa bahkan jikaconst_blob
memiliki anggota yang berbeda dariblob
hanya dalam "keteguhan tidak langsung", tipestruct blob*
ke astruct const_blob*
akan dianggap berbeda untuk keperluan aturan alias yang ketat. Akibatnya, jika kode melemparkanblob*
ke aconst_blob*
, setiap penulisan berikutnya ke struktur yang mendasari menggunakan satu jenis akan secara diam-diam membatalkan petunjuk yang ada dari jenis lain, sehingga setiap penggunaan akan memanggil Perilaku Tidak Terdefinisi (yang biasanya tidak berbahaya, tetapi bisa mematikan) .Halaman 150 dari Tutorial Perakitan PC di http://www.drpaulcarter.com/pcasm/ memiliki penjelasan yang jelas tentang bagaimana C memungkinkan suatu fungsi mengembalikan sebuah struct:
Saya menggunakan kode C berikut untuk memverifikasi pernyataan di atas:
Gunakan "gcc -S" untuk menghasilkan perakitan untuk bagian kode C ini:
Tumpukan sebelum panggilan dibuat:
Tumpukan tepat setelah memanggil buat:
sumber
Saya hanya ingin menunjukkan satu keuntungan melewati struct Anda dengan nilai adalah bahwa kompiler pengoptimalisasi mungkin lebih baik mengoptimalkan kode Anda.
sumber