String literal: Kemana mereka pergi?

161

Saya tertarik di mana string literal dialokasikan / disimpan.

Saya menemukan satu jawaban yang menarik di sini , dengan mengatakan:

Mendefinisikan inline string sebenarnya menyematkan data dalam program itu sendiri dan tidak dapat diubah (beberapa kompiler mengizinkan ini dengan trik pintar, jangan repot-repot).

Tapi, itu ada hubungannya dengan C ++, belum lagi dikatakan tidak repot.

Saya mengganggu. = D

Jadi pertanyaan saya adalah di mana dan bagaimana string string saya disimpan? Mengapa saya tidak mencoba mengubahnya? Apakah implementasinya bervariasi berdasarkan platform? Apakah ada yang peduli untuk menguraikan "trik pintar?"

Chris Cooper
sumber

Jawaban:

125

Teknik yang umum adalah string literal diletakkan di bagian "read-only-data" yang dipetakan ke dalam ruang proses sebagai read-only (itulah sebabnya Anda tidak dapat mengubahnya).

Itu bervariasi berdasarkan platform. Misalnya, arsitektur chip yang lebih sederhana mungkin tidak mendukung segmen memori hanya baca sehingga segmen data akan dapat ditulis.

Alih-alih mencoba mencari cara untuk membuat string literal dapat diubah (itu akan sangat tergantung pada platform Anda dan dapat berubah seiring waktu), cukup gunakan array:

char foo[] = "...";

Kompiler akan mengatur agar array diinisialisasi dari literal dan Anda dapat memodifikasi array.

R Samuel Klatchko
sumber
5
Ya, saya menggunakan array ketika saya ingin memiliki string yang bisa berubah. Saya penasaran. Terima kasih.
Chris Cooper
2
Anda memang harus berhati-hati tentang buffer overflow saat menggunakan array untuk string yang dapat berubah, - cukup menulis string lebih panjang dari panjang array (misalnya foo = "hello"dalam kasus ini) dapat menyebabkan efek samping yang tidak diinginkan ... (dengan asumsi Anda tidak kembali mengalokasikan memori dengan newatau sesuatu)
johnny
2
Apakah saat menggunakan string array masuk stack atau di tempat lain?
Suraj Jain
Tidak bisakah kita menggunakan char *p = "abc";untuk membuat string yang dapat berubah seperti dikatakan berbeda oleh @ChrisCooper
KPMG
52

Tidak ada satu jawaban untuk ini. Standar C dan C ++ hanya mengatakan bahwa literal string memiliki durasi penyimpanan statis, setiap upaya untuk memodifikasinya memberikan perilaku yang tidak terdefinisi, dan beberapa string literal dengan konten yang sama mungkin atau mungkin tidak berbagi penyimpanan yang sama.

Bergantung pada sistem yang Anda gunakan untuk menulis, dan kemampuan format file yang dapat dieksekusi yang digunakannya, mereka dapat disimpan bersama dengan kode program di segmen teks, atau mereka mungkin memiliki segmen terpisah untuk data yang diinisialisasi.

Menentukan detail akan bervariasi tergantung pada platform juga - kemungkinan besar termasuk alat yang dapat memberi tahu Anda di mana meletakkannya. Beberapa bahkan akan memberi Anda kendali atas perincian seperti itu, jika Anda menginginkannya (mis. Gnu ld memungkinkan Anda untuk menyediakan skrip untuk menceritakan semuanya tentang cara mengelompokkan data, kode, dll.)

Jerry Coffin
sumber
1
Saya merasa tidak mungkin bahwa data string akan disimpan langsung di segmen .text. Untuk liter yang benar-benar singkat, saya dapat melihat kode pembuat kompiler seperti movb $65, 8(%esp); movb $66, 9(%esp); movb $0, 10(%esp)untuk string "AB", tetapi sebagian besar waktu, itu akan berada di segmen non-kode seperti .dataatau .rodataatau sejenisnya (tergantung pada apakah target mendukung atau tidak segmen hanya baca).
Adam Rosenfield
Jika string literal valid untuk seluruh durasi program, bahkan selama penghancuran objek statis maka apakah valid untuk mengembalikan referensi const ke string literal? Mengapa program ini menampilkan kesalahan runtime, lihat ideone.com/FTs1Ig
Destructor
@AdamRosenfield: Jika Anda bosan kadang-kadang, Anda mungkin ingin melihat (misalnya) format warisan UNIX a.out (misalnya, freebsd.org/cgi/… ). Satu hal yang harus Anda perhatikan dengan cepat adalah hanya mendukung satu segmen data, yang selalu dapat ditulis. Jadi jika Anda ingin string literal baca-saja, pada dasarnya satu-satunya tempat mereka dapat pergi adalah segmen teks (dan ya, pada saat itu linker sering melakukan hal itu).
Jerry Coffin
48

Mengapa saya tidak mencoba mengubahnya?

Karena itu perilaku yang tidak terdefinisi. Kutipan dari C99 N1256 draft 6.7.8 / 32 "Inisialisasi" :

CONTOH 8: Deklarasi

char s[] = "abc", t[3] = "abc";

mendefinisikan objek char array "polos" sdan tyang elemennya diinisialisasi dengan literal karakter string.

Deklarasi ini identik dengan

char s[] = { 'a', 'b', 'c', '\0' },
t[] = { 'a', 'b', 'c' };

Isi array dapat dimodifikasi. Di sisi lain, deklarasi

char *p = "abc";

mendefinisikan pdengan tipe "pointer to char" dan menginisialisasi untuk menunjuk ke objek dengan tipe "array of char" dengan panjang 4 yang elemennya diinisialisasi dengan karakter string literal. Jika upaya dilakukan untuk digunakan puntuk mengubah konten array, perilaku tersebut tidak ditentukan.

Kemana mereka pergi?

GCC 4.8 x86-64 ELF Ubuntu 14.04:

  • char s[]: tumpukan
  • char *s:
    • .rodata bagian dari file objek
    • segmen yang sama di mana .textbagian dari file objek akan dibuang, yang memiliki izin Baca dan Exec, tetapi tidak Tulis

Program:

#include <stdio.h>

int main() {
    char *s = "abc";
    printf("%s\n", s);
    return 0;
}

Kompilasi dan dekompilasi:

gcc -ggdb -std=c99 -c main.c
objdump -Sr main.o

Output berisi:

 char *s = "abc";
8:  48 c7 45 f8 00 00 00    movq   $0x0,-0x8(%rbp)
f:  00 
        c: R_X86_64_32S .rodata

Jadi string disimpan di .rodatabagian tersebut.

Kemudian:

readelf -l a.out

Berisi (disederhanakan):

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
                 0x0000000000000704 0x0000000000000704  R E    200000

 Section to Segment mapping:
  Segment Sections...
   02     .text .rodata

Ini berarti bahwa skrip linker default membuang keduanya .textdan .rodatake dalam segmen yang dapat dieksekusi tetapi tidak dimodifikasi ( Flags = R E). Mencoba untuk memodifikasi segmen seperti itu mengarah ke segfault di Linux.

Jika kami melakukan hal yang sama untuk char[]:

 char s[] = "abc";

kami memperoleh:

17:   c7 45 f0 61 62 63 00    movl   $0x636261,-0x10(%rbp)

jadi itu disimpan dalam tumpukan (relatif terhadap %rbp), dan tentu saja kita dapat memodifikasinya.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
sumber
22

FYI, hanya mencadangkan jawaban lainnya:

Standar: ISO / IEC 14882: 2003 mengatakan:

2.13. Literal string

  1. [...] Literal string biasa memiliki tipe "array of n const char" dan durasi penyimpanan statis (3,7)

  2. Apakah semua string literal berbeda (yaitu, disimpan dalam objek yang tidak tumpang tindih) ditentukan oleh implementasi. Efek dari upaya untuk memodifikasi string literal tidak ditentukan.

Justicle
sumber
2
Informasi yang bermanfaat, tetapi tautan pemberitahuan untuk C ++, sedangkan pertanyaan dialihkan ke c
Grijesh Chauhan
1
dikonfirmasi # 2 di 2.13. Dengan opsi -Os (mengoptimalkan ukuran), gcc tumpang tindih literal string dalam .rodata.
Peng Zhang
14

gcc membuat .rodatabagian yang dipetakan "di suatu tempat" di ruang alamat dan ditandai hanya baca,

Visual C ++ ( cl.exe) membuat .rdatabagian untuk tujuan yang sama.

Anda dapat melihat output dari dumpbinatau objdump(di Linux) untuk melihat bagian yang dapat dieksekusi.

Misalnya

>dumpbin vec1.exe
Microsoft (R) COFF/PE Dumper Version 8.00.50727.762
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file vec1.exe

File Type: EXECUTABLE IMAGE

  Summary

        4000 .data
        5000 .rdata  <-- here are strings and other read-only stuff.
       14000 .text
Alex Budovski
sumber
1
Saya tidak bisa melihat cara mendapatkan pembongkaran bagian rdata dengan objdump.
user2284570
@ user2284570, itu karena bagian itu tidak mengandung perakitan. Ini berisi data.
Alex Budovski
1
Hanya masalah untuk mendapatkan hasil yang lebih mudah dibaca. Maksud saya, saya ingin mendapatkan string dengan pembongkaran alih-alih alamat ke bagian tersebut. (hem Anda tahu, printf("some null terminated static string");bukan printf(*address);di C)
user2284570
4

Itu tergantung pada format executable Anda . Salah satu cara untuk memikirkannya adalah bahwa jika Anda memprogram perakitan, Anda dapat menempatkan string literal di segmen data program perakitan Anda. Kompiler C Anda melakukan sesuatu seperti itu, tetapi semuanya tergantung pada sistem apa yang sedang Anda biner kompilasi.

Parappa
sumber
2

Literal string sering dialokasikan ke memori hanya-baca, menjadikannya tidak berubah. Namun, dalam beberapa modifikasi kompiler dimungkinkan oleh "trik pintar" .. Dan trik pintar adalah dengan "menggunakan penunjuk karakter yang menunjuk ke memori" .. ingat beberapa kompiler, mungkin tidak mengizinkan ini..Ini adalah demo

char *tabHeader = "Sound";
*tabHeader = 'L';
printf("%s\n",tabHeader); // Displays "Lound"
Sahil Jain
sumber
0

Karena ini mungkin berbeda dari kompiler ke kompiler, cara terbaik adalah memfilter dump objek untuk string literal yang dicari:

objdump -s main.o | grep -B 1 str

di mana -skekuatan objdumpuntuk menampilkan isi penuh dari semua bagian, main.oadalah file objek, -B 1memaksa grepuntuk juga mencetak satu baris sebelum pertandingan (sehingga Anda dapat melihat nama bagian) dan strmerupakan string literal yang Anda cari.

Dengan gcc pada mesin Windows, dan satu variabel dinyatakan dalam mainlike

char *c = "whatever";

berlari

objdump -s main.o | grep -B 1 whatever

kembali

Contents of section .rdata:
 0000 77686174 65766572 00000000           whatever....
mihai
sumber