สตริงตัวอักษร: พวกเขาจะไปที่ไหน


161

ฉันสนใจที่ตัวอักษรสตริงได้รับการจัดสรร / จัดเก็บ

ฉันพบคำตอบที่น่าสนใจที่นี่โดยพูดว่า:

การกำหนดสตริงแบบอินไลน์ฝังข้อมูลลงในโปรแกรมของตัวเองและไม่สามารถเปลี่ยนแปลงได้ (คอมไพเลอร์บางตัวยอมให้สิ่งนี้ทำได้โดยใช้เล่ห์เหลี่ยมไม่ต้องกังวล)

แต่มันเกี่ยวข้องกับ C ++ ไม่ต้องพูดถึงว่ามันไม่รบกวน

ฉันกำลังยุ่ง = D

ดังนั้นคำถามของฉันอยู่ที่ไหนสตริงตัวอักษรของฉันจะถูกเก็บไว้อย่างไร? ทำไมฉันไม่ลองเปลี่ยนมันล่ะ? การใช้งานแตกต่างกันไปตามแพลตฟอร์มหรือไม่ ไม่มีใครสนใจที่จะอธิบายรายละเอียดเกี่ยวกับ "เคล็ดลับสมาร์ท?"

คำตอบ:


125

เทคนิคทั่วไปสำหรับสตริงตัวอักษรที่จะใส่ในส่วน "อ่านอย่างเดียวข้อมูล" ซึ่งได้รับการแมปในพื้นที่กระบวนการเป็นอ่านอย่างเดียว (ซึ่งเป็นเหตุผลที่คุณไม่สามารถเปลี่ยนได้)

มันแตกต่างกันไปตามแพลตฟอร์ม ตัวอย่างเช่นสถาปัตยกรรมชิปที่ง่ายกว่าอาจไม่สนับสนุนกลุ่มหน่วยความจำแบบอ่านอย่างเดียวดังนั้นกลุ่มข้อมูลจะสามารถเขียนได้

จากนั้นลองหาเคล็ดลับในการทำให้ตัวอักษรสตริงเปลี่ยนแปลงได้ (ขึ้นอยู่กับแพลตฟอร์มของคุณและสามารถเปลี่ยนแปลงได้ตลอดเวลา) เพียงใช้อาร์เรย์:

char foo[] = "...";

คอมไพเลอร์จะจัดเรียงอาร์เรย์เพื่อรับค่าเริ่มต้นจากตัวอักษรและคุณสามารถปรับเปลี่ยนอาร์เรย์ได้


5
ใช่ฉันใช้อาร์เรย์เมื่อฉันต้องการมีสตริงที่ไม่แน่นอน ผมก็แค่อยากรู้. ขอบคุณ
Chris Cooper

2
คุณจะต้องระมัดระวังเกี่ยวกับบัฟเฟอร์ล้นเมื่อใช้อาร์เรย์สำหรับสตริงที่ไม่แน่นอน แต่ - เพียงแค่เขียนสตริงที่ยาวกว่าความยาวของอาร์เรย์ (เช่นfoo = "hello"ในกรณีนี้) อาจทำให้เกิดผลข้างเคียงที่ไม่ได้ตั้งใจ ... (สมมติว่าคุณไม่ใช่ การจัดสรรหน่วยความจำnewหรือบางอย่าง)
จอห์นนี่

2
เมื่อใช้สตริงอาเรย์ไปในกองหรือที่อื่น?
Suraj Jain

เราไม่สามารถใช้char *p = "abc";สร้างสตริงที่ไม่แน่นอนดังที่ @ChrisCooper พูดต่างกัน
KPMG

52

ไม่มีคำตอบสำหรับเรื่องนี้ มาตรฐาน C และ C ++ เพียงบอกว่าตัวอักษรสตริงมีระยะเวลาการจัดเก็บแบบคงที่ความพยายามใด ๆ ในการปรับเปลี่ยนพวกเขาให้พฤติกรรมที่ไม่ได้กำหนดและตัวอักษรสตริงหลายตัวที่มีเนื้อหาเดียวกันอาจหรือไม่อาจใช้ที่เก็บข้อมูลเดียวกัน

ขึ้นอยู่กับระบบที่คุณกำลังเขียนและความสามารถของรูปแบบไฟล์ปฏิบัติการที่ใช้มันอาจถูกจัดเก็บพร้อมกับรหัสโปรแกรมในส่วนข้อความหรืออาจมีส่วนแยกต่างหากสำหรับข้อมูลเริ่มต้น

การพิจารณารายละเอียดจะแตกต่างกันไปขึ้นอยู่กับแพลตฟอร์มเช่นกันส่วนใหญ่อาจรวมถึงเครื่องมือที่สามารถบอกคุณได้ว่าวางไว้ที่ไหน บางคนจะให้คุณควบคุมรายละเอียดเช่นนั้นถ้าคุณต้องการ (เช่น gnu ld ช่วยให้คุณสามารถส่งสคริปต์เพื่อบอกทุกอย่างเกี่ยวกับวิธีจัดกลุ่มข้อมูลรหัส ฯลฯ )


1
ฉันคิดว่ามันไม่น่าเป็นไปได้ที่ข้อมูลสตริงจะถูกจัดเก็บโดยตรงในเซ็กเมนต์. text สำหรับตัวอักษรสั้นจริงๆผมสามารถมองเห็นรหัสสร้างเรียบเรียงเช่นmovb $65, 8(%esp); movb $66, 9(%esp); movb $0, 10(%esp)สตริง"AB"แต่ส่วนใหญ่ของเวลาที่มันจะอยู่ในส่วนที่ไม่ใช่รหัสเช่น.dataหรือ.rodataหรือชอบ (ขึ้นอยู่กับว่าหรือไม่สนับสนุนเป้าหมาย กลุ่มที่อ่านอย่างเดียว)
Adam Rosenfield

หากตัวอักษรสตริงถูกต้องตลอดระยะเวลาของโปรแกรมแม้ในระหว่างการทำลายวัตถุคงที่มันจะถูกต้องหรือไม่ในการส่งคืนการอ้างอิง const ไปยังตัวอักษรสตริง? เหตุใดโปรแกรมนี้จึงแสดงข้อผิดพลาดรันไทม์ให้ดูideone.com/FTs1Ig
Destructor

@AdamRosenfield: ถ้าคุณเบื่อบางครั้งคุณอาจต้องการดู (ตัวอย่างหนึ่ง) รูปแบบ UNIX a.out ดั้งเดิม (เช่นfreebsd.org/cgi/ … ) สิ่งหนึ่งที่คุณควรสังเกตอย่างรวดเร็วคือรองรับเฉพาะส่วนข้อมูลเดียวซึ่งเขียนได้เสมอ ดังนั้นหากคุณต้องการตัวอักษรสตริงอ่านอย่างเดียวที่สำคัญพวกเขาสามารถไปได้คือเซ็กเมนต์ข้อความ (และใช่ในเวลาที่ลิงเกอร์ลิงก์ทำเช่นนั้นบ่อยๆ)
Jerry Coffin

48

ทำไมฉันไม่ลองเปลี่ยนมันล่ะ?

เพราะมันเป็นพฤติกรรมที่ไม่ได้กำหนด อ้างอิงจากC99 N1256 ฉบับร่าง 6.7.8 / 32 "การเริ่มต้น" :

ตัวอย่างที่ 8: การประกาศ

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

กำหนดวัตถุอาร์เรย์ถ่าน "ธรรมดา" sและtองค์ประกอบที่จะเริ่มต้นด้วยตัวอักษรสตริงตัวอักษร

คำประกาศนี้เหมือนกัน

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

เนื้อหาของอาร์เรย์สามารถแก้ไขได้ ในทางกลับกันการประกาศ

char *p = "abc";

กำหนดpด้วยประเภท "ตัวชี้ไปที่ถ่าน" และเริ่มต้นให้ชี้ไปที่วัตถุที่มีประเภท "อาร์เรย์ของถ่าน" ที่มีความยาว 4 ซึ่งองค์ประกอบจะเริ่มต้นด้วยตัวอักษรสตริงตัวอักษร หากมีความพยายามในการใช้pเพื่อปรับเปลี่ยนเนื้อหาของอาร์เรย์พฤติกรรมจะไม่ได้กำหนด

พวกเขาไปไหน?

GCC 4.8 x86-64 ELF Ubuntu 14.04:

  • char s[]: ซ้อนกัน
  • char *s:
    • .rodata ส่วนของไฟล์วัตถุ
    • เซ็กเมนต์เดียวกันที่.textส่วนของไฟล์อ็อบเจ็กต์ได้รับการดัมพ์ซึ่งมีสิทธิ์ Read และ Exec แต่ไม่ใช่ Write

โปรแกรม:

#include <stdio.h>

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

รวบรวมและถอดรหัส:

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

เอาท์พุทประกอบด้วย:

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

ดังนั้นสตริงจะถูกเก็บไว้ใน.rodataส่วน

แล้ว:

readelf -l a.out

มี (ประยุกต์):

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

ซึ่งหมายความว่าสคริปต์ตัวเชื่อมโยงค่าเริ่มต้นจะทิ้งทั้งสองอย่าง.textและ.rodataไปยังส่วนที่สามารถดำเนินการได้ แต่ไม่ได้แก้ไข ( Flags = R E) ความพยายามที่จะแก้ไขส่วนดังกล่าวนำไปสู่ ​​segfault ใน Linux

ถ้าเราทำเช่นเดียวกันสำหรับchar[]:

 char s[] = "abc";

เราได้รับ:

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

ดังนั้นมันจึงถูกเก็บไว้ในสแต็ก (สัมพันธ์กับ%rbp) และแน่นอนว่าเราสามารถแก้ไขได้


22

FYI เพียงสำรองคำตอบอื่น ๆ :

มาตรฐาน: ISO / IEC 14882: 2003พูดว่า:

2.13 สตริงตัวอักษร

  1. [... ] สตริงตัวอักษรธรรมดามีประเภท "อาร์เรย์ของn const char" และระยะเวลาการจัดเก็บแบบคงที่ (3.7)

  2. ไม่ว่าสตริงตัวอักษรทั้งหมดจะแตกต่างกัน (นั่นคือจะถูกเก็บไว้ในวัตถุ nonoverlapping) มีการใช้งานที่กำหนดไว้ ผลของการพยายามแก้ไขสตริงตัวอักษรจะไม่ได้กำหนด


2
ข้อมูลที่เป็นประโยชน์ แต่การเชื่อมโยงแจ้งให้ทราบล่วงหน้าสำหรับ C ++ ในขณะที่คำถามจะ tanged เพื่อ
Grijesh Chauhan

1
ยืนยันหมายเลข 2 ใน 2.13 ด้วยตัวเลือก -Os (ปรับขนาดให้เหมาะสม) gcc จะทับซ้อนตัวอักษรของสตริงใน .rodata
Peng Zhang

14

gcc ทำให้.rodataส่วนที่ได้รับการแมป "ที่ไหนสักแห่ง" ในพื้นที่ที่อยู่และถูกทำเครื่องหมายอ่านอย่างเดียว

Visual C ++ ( cl.exe) สร้าง.rdataหัวข้อสำหรับจุดประสงค์เดียวกัน

คุณสามารถดูผลลัพธ์จากdumpbinหรือobjdump(บน Linux) เพื่อดูส่วนของปฏิบัติการของคุณ

เช่น

>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
ฉันไม่เห็นวิธีการถอดชิ้นส่วน rdata ด้วย objdump
2284570

@ user2284570 นั่นเป็นเพราะส่วนนั้นไม่มีชุดประกอบ มันมีข้อมูล
Alex Budovski

1
เป็นเพียงเรื่องที่จะได้รับผลลัพธ์ที่อ่านได้มากขึ้น ฉันหมายความว่าฉันต้องการรับสายที่มีการถอดการเชื่อมต่อแทนที่จะเป็นที่อยู่ในส่วนเหล่านั้น (ล้อมรอบคุณรู้printf("some null terminated static string");แทนprintf(*address);ใน C)
user2284570

4

มันขึ้นอยู่กับรูปแบบที่คุณปฏิบัติการ วิธีหนึ่งที่จะคิดเกี่ยวกับมันคือถ้าคุณเป็นแอสเซมบลีการเขียนโปรแกรมคุณอาจใส่ตัวอักษรสตริงในส่วนข้อมูลของแอสเซมบลีของคุณ คอมไพเลอร์ C ของคุณทำอะไรแบบนั้น แต่ทุกอย่างขึ้นอยู่กับระบบที่คุณกำลังรวบรวมไบนารี่


2

สตริงตัวอักษรมักถูกจัดสรรไปยังหน่วยความจำแบบอ่านอย่างเดียวทำให้พวกมันไม่เปลี่ยนรูป อย่างไรก็ตามในการปรับแต่งคอมไพเลอร์บางอย่างเป็นไปได้โดย "สมาร์ทเคล็ดลับ" .. และสมาร์ทเคล็ดลับคือ "ใช้ตัวชี้ที่ชี้ไปที่หน่วยความจำ" .. จำคอมไพเลอร์บางอย่างอาจไม่อนุญาตนี้ .. นี่คือตัวอย่าง

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

0

เนื่องจากสิ่งนี้อาจแตกต่างจากคอมไพเลอร์ถึงคอมไพเลอร์วิธีที่ดีที่สุดคือการกรองดัมพ์วัตถุสำหรับสตริงตัวอักษรที่ค้นหา:

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

ที่-sบังคับobjdumpให้แสดงเนื้อหาทั้งหมดของทุกส่วนmain.oคือไฟล์อ็อบเจ็กต์-B 1บังคับgrepให้พิมพ์หนึ่งบรรทัดก่อนการแข่งขัน (เพื่อให้คุณสามารถเห็นชื่อส่วน) และstrเป็นสตริงตัวอักษรที่คุณกำลังค้นหา

ด้วย gcc บนเครื่อง Windows และมีตัวแปรหนึ่งตัวที่ประกาศmainเหมือนกัน

char *c = "whatever";

วิ่ง

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

ผลตอบแทน

Contents of section .rdata:
 0000 77686174 65766572 00000000           whatever....
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.