Dize değişmezleri: Nereye gidiyorlar?


161

Ben dize değişmezleri tahsis / depolanan nerede ilgileniyorum.

Burada ilginç bir cevap buldum :

Satır içi bir satır tanımlamak aslında verileri programın içine gömer ve değiştirilemez (bazı derleyiciler buna akıllı bir hile ile izin verir, rahatsız etmeyin).

Ancak, rahatsız etmemesini söylediğinden bahsetmemek için C ++ ile ilgisi vardı.

Rahatsız ediyorum. = D

Benim sorum şu ki, dizgi hazır bilgim nerede ve nasıl tutulur? Neden değiştirmeye çalışmamalıyım? Uygulama platforma göre değişiyor mu? Herkes "akıllı hile" üzerinde durmaya özen gösteriyor mu?

Yanıtlar:


125

Yaygın bir teknik, dizgi değişmezlerinin işlem alanına salt okunur olarak eşlenen "salt okunur veriler" bölümüne yerleştirilmesidir (bu nedenle değiştiremezsiniz).

Platforma göre değişir. Örneğin, daha basit yonga mimarileri, salt okunur bellek segmentlerini desteklemeyebilir, bu nedenle veri segmenti yazılabilir olacaktır.

Daha sonra, dizgi değişmezlerini değiştirilebilir hale getirmek için bir numara bulmaya çalışın (platformunuza oldukça bağımlı olacak ve zamanla değişebilir), sadece dizileri kullanın:

char foo[] = "...";

Derleyici dizinin değişmez değerden başlatılmasını ayarlar ve diziyi değiştirebilirsiniz.


5
Evet, değişken dizeler kullanmak istediğimde diziler kullanıyorum. Sadece merak ettim. Teşekkürler.
Chris Cooper

2
Değişken dizeler için diziler kullanırken arabellek taşması konusunda dikkatli olmanız gerekir, ancak - sadece dizi uzunluğundan daha uzun bir dize yazmak (örneğin foo = "hello"bu durumda) istenmeyen yan etkilere neden olabilir ... newya da bir şey ile bellek ayırma )
johnny

2
Dizi dizesini kullanırken yığın veya başka bir yere gider mi?
Suraj Jain

char *p = "abc";@ChrisCooper tarafından söylendiği gibi değişebilir dizeler yapmak için kullanamaz mıyız
KPMG

52

Buna kimse cevap vermiyor. C ve C ++ standartları, dize değişmezlerinin statik depolama süresine sahip olduğunu, bunları değiştirme girişimlerinin tanımsız davranışlar verdiğini ve aynı içeriğe sahip birden çok dize değişmezinin aynı depolama alanını paylaşabileceğini veya paylaşmayabileceğini söylüyor.

Yazdığınız sisteme ve kullandığı yürütülebilir dosya biçiminin özelliklerine bağlı olarak, metin segmentindeki program koduyla birlikte saklanabilir veya başlatılan veriler için ayrı bir segmenti olabilir.

Ayrıntıları belirlemek de platforma bağlı olarak değişecektir - büyük olasılıkla size nereye koyduğunu söyleyebilecek araçlar içerir. Hatta bazıları, bunun gibi ayrıntılar üzerinde kontrol sahibi olmanızı sağlar, eğer isterseniz (örn. Gnu ld, veri, kod vb.


1
Dize verilerinin doğrudan .text segmentinde depolanması pek olası değildir. Gerçekten kısa değişmezleri için, ben gibi derleyici üreten kodunu görebiliyordu movb $65, 8(%esp); movb $66, 9(%esp); movb $0, 10(%esp)dize için "AB", ama zaman büyük çoğunluğu, bu gibi olmayan bir kod segmentinde olacak .dataya .rodataya olsun ya da olmasın, hedef desteklediğine bağlı olarak (gibi salt okunur segmentler).
Adam Rosenfield

Dizgi değişmezleri, programın tüm süresi boyunca geçerliyse, statik nesnelerin imhası sırasında bile, dizgi değişmezine sabit başvuru döndürmek geçerli midir? Bu program neden çalışma zamanı hatası gösteriyor bkz. İdeone.com/FTs1Ig
Destructor

@AdamRosenfield: Bazen sıkılırsanız, eski UNIX a.out biçimine (örneğin, freebsd.org/cgi/… ) bakmak isteyebilirsiniz . Hızlı bir şekilde fark etmeniz gereken bir şey, her zaman yazılabilir olan sadece bir veri segmentini desteklemesidir. Eğer salt okunur dize hazır istiyorsanız, esasen yalnızca yerleştirmek olabilir gitmek metin segmenti (ve evet, zaman bağlayıcılarda sıkça tam olarak yaptım).
Jerry Coffin

48

Neden değiştirmeye çalışmamalıyım?

Çünkü tanımsız bir davranış. Alıntı C99 N1256 taslak 6.7.8 / 32 "Başlatma" :

ÖRNEK 8: Beyan

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

"düz" karakter dizisi nesnelerini tanımlar sve töğeleri karakter dizesi değişmezleriyle başlatılır.

Bu beyan aşağıdakilerle aynıdır:

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

Dizilerin içeriği değiştirilebilir. Öte yandan, deklarasyon

char *p = "abc";

p"işaretçi karakterine" türüyle tanımlar ve bunu, karakter karakter dizisi ile başlatılan 4 uzunluklu "karakter dizisi" türüne sahip bir nesneyi işaret edecek şekilde başlatır. pDizinin içeriğini değiştirmek için bir girişimde bulunulursa , davranış tanımsızdır.

Nereye gidiyorlar?

GCC 4.8 x86-64 ELF Ubuntu 14.04:

  • char s[]: yığın
  • char *s:
    • .rodata nesne dosyasının bölümü
    • .textnesne dosyasının bölümünün Okuma ve Yürütme izinlerine sahip olan ancak Yazma bölümünün dökümünün alındığı bölüm

Programı:

#include <stdio.h>

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

Derleme ve derleme:

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

Çıktı şunları içerir:

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

Böylece dize .rodatabölümde saklanır .

Sonra:

readelf -l a.out

İçerik (basitleştirilmiş):

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

Varsayılan bağlayıcı komut, hem döker Bu araçlar .textve .rodatayürütülür, ancak değiştirilmemiş olabilir, bir segmente ( Flags = R E). Böyle bir segmenti değiştirmeye çalışmak Linux'ta bir segfault'a yol açar.

Eğer aynısını şu şekilde yaparsak char[]:

 char s[] = "abc";

elde ederiz:

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

böylece yığınta depolanır (göreceli olarak %rbp) ve elbette değiştirebiliriz.


22

Bilginize, diğer cevapları yedekliyorum:

Standart: ISO / IEC 14882: 2003 diyor ki:

2.13. Dize değişmez değerleri

  1. [...] Sıradan bir dize değişmez değeri “dizi n const char” ve statik depolama süresine sahiptir (3.7)

  2. Tüm dize değişmezlerinin farklı olup olmadığı (yani, çakışmayan nesnelerde saklanıp depolanmadığı) uygulama tanımlıdır. Bir dizgi değişmezini değiştirmeye çalışmanın etkisi tanımlanmamıştır.


2
Faydalı bilgiler, ancak uyarı bağlantısı C ++ içindir, ancak soru c'ye karıştırılır
Grijesh Chauhan

1
2.13'te # 2 doğruladı. -Os seçeneğiyle (boyut için optimize et) gcc, .rodata'daki dize değişmezleriyle çakışır.
Peng Zhang

14

gcc .rodataadres alanında "bir yere" eşlenen ve salt okunur olarak işaretlenmiş bir bölüm oluşturur,

Visual C ++ ( cl.exe) .rdataaynı amaçla bir bölüm yapar .

Yürütülebilir dosyalarınızın bölümlerini görmek için dumpbinveya objdump(Linux'ta) çıktıya bakabilirsiniz .

Örneğin

>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

1
Obdump ile rdata bölümünün nasıl söküleceğini göremiyorum.
user2284570

@ user2284570, çünkü bu bölüm montaj içermiyor. Veri içerir.
Alex Budovski

1
Daha okunabilir çıktı elde etmek için bir mesele. Demek istediğim, bu bölümlerin adresi yerine dizgileri sökme işlemiyle satır içine almak istiyorum. ( C printf("some null terminated static string");yerine biliyorsunuz printf(*address);)
user2284570

4

Bu bağlıdır formatında senin içinde yürütülebilir . Bunu düşünmenin bir yolu, montaj programlaması yapıyorsanız, montaj programınızın veri segmentine dize değişmezleri koyabileceğinizdir. C derleyiciniz böyle bir şey yapar, ancak her şey ikili olarak hangi sistem için derlendiğinize bağlıdır.


2

Dize değişmezleri genellikle salt okunur belleğe ayrılır ve değiştirilemez hale gelir. Ancak, bazı derleyiciler bir "akıllı hile" ile değişiklik mümkündür .. Ve akıllı hile "hafıza işaret eden işaretçi işaretçisi kullanarak" .. bazı derleyiciler hatırlıyorum, buna izin vermeyebilir .. İşte demo

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

0

Bu, derleyiciden derleyiciye farklılık gösterebileceğinden, en iyi yol, aranan dize değişmez değeri için bir nesne dökümü filtrelemektir:

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

nerede -skuvvetleri objdumpbütün kesimlerinin tam içeriğini görüntülemek için, main.onesne dosyasıdır -B 1güçleri grepde maçın (böylece bölüm adını görülebilecektir) önce bir satır yazdırmak ve striçin sen arama değişmezi dizedir.

Bir Windows makinesinde gcc ve bir değişken maingibi bildirildi

char *c = "whatever";

çalışan

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

İadeler

Contents of section .rdata:
 0000 77686174 65766572 00000000           whatever....
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.