Chuỗi ký tự: Họ đi đâu?


161

Tôi quan tâm đến nơi chuỗi ký tự được phân bổ / lưu trữ.

Tôi đã tìm thấy một câu trả lời hấp dẫn ở đây , nói:

Xác định một chuỗi nội tuyến thực sự nhúng dữ liệu trong chính chương trình và không thể thay đổi (một số trình biên dịch cho phép điều này bằng một thủ thuật thông minh, đừng bận tâm).

Nhưng, nó phải làm với C ++, chưa kể rằng nó nói không làm phiền.

Tôi đang làm phiền = D

Vì vậy, câu hỏi của tôi là ở đâu và làm thế nào là chuỗi ký tự của tôi được giữ? Tại sao tôi không nên cố gắng thay đổi nó? Liệu việc thực hiện thay đổi theo nền tảng? Có ai quan tâm đến việc xây dựng "thủ thuật thông minh?"

Câu trả lời:


125

Một kỹ thuật phổ biến là để các chuỗi ký tự được đặt trong phần "chỉ đọc dữ liệu" được ánh xạ vào không gian xử lý dưới dạng chỉ đọc (đó là lý do tại sao bạn không thể thay đổi nó).

Nó không thay đổi theo nền tảng. Ví dụ, kiến ​​trúc chip đơn giản hơn có thể không hỗ trợ các phân đoạn bộ nhớ chỉ đọc nên phân đoạn dữ liệu sẽ có thể ghi được.

Thay vào đó, sau đó cố gắng tìm ra một mẹo để làm cho chuỗi ký tự thay đổi (nó sẽ phụ thuộc nhiều vào nền tảng của bạn và có thể thay đổi theo thời gian), chỉ cần sử dụng mảng:

char foo[] = "...";

Trình biên dịch sẽ sắp xếp cho mảng được khởi tạo từ chữ và bạn có thể sửa đổi mảng.


5
Có, tôi sử dụng mảng khi tôi muốn có chuỗi có thể thay đổi. Tôi chỉ là tò mò thôi. Cảm ơn.
Chris Cooper

2
Tuy nhiên, bạn phải cẩn thận về lỗi tràn bộ đệm khi sử dụng mảng cho các chuỗi có thể thay đổi, tuy nhiên - chỉ cần viết một chuỗi dài hơn độ dài mảng (ví dụ foo = "hello"trong trường hợp này) có thể gây ra tác dụng phụ ngoài ý muốn ... (giả sử bạn không tái cấp phát bộ nhớ với newhoặc một cái gì đó)
johnny

2
Có khi sử dụng chuỗi mảng đi trong ngăn xếp hoặc ở nơi khác?
Suraj Jain

Chúng tôi không thể sử dụng char *p = "abc";để tạo các chuỗi có thể thay đổi như đã nói khác nhau bởi @ChrisCooper
KPMG

52

Không có ai trả lời cho điều này. Các tiêu chuẩn C và C ++ chỉ nói rằng các chuỗi ký tự chuỗi có thời lượng lưu trữ tĩnh, mọi nỗ lực sửa đổi chúng đều cho hành vi không xác định và nhiều chuỗi ký tự có cùng nội dung có thể hoặc không thể chia sẻ cùng một bộ lưu trữ.

Tùy thuộc vào hệ thống bạn đang viết và khả năng của định dạng tệp thực thi mà nó sử dụng, chúng có thể được lưu trữ cùng với mã chương trình trong phân đoạn văn bản hoặc chúng có thể có một phân đoạn riêng cho dữ liệu khởi tạo.

Việc xác định các chi tiết cũng sẽ khác nhau tùy thuộc vào nền tảng - hầu hết có thể bao gồm các công cụ có thể cho bạn biết nơi đặt nó. Một số thậm chí sẽ cung cấp cho bạn quyền kiểm soát các chi tiết như thế, nếu bạn muốn nó (ví dụ: gnu ld cho phép bạn cung cấp một tập lệnh để cho tất cả biết về cách nhóm dữ liệu, mã, v.v.)


1
Tôi thấy rằng dữ liệu chuỗi sẽ không được lưu trữ trực tiếp trong phân đoạn .text. Đối với các chữ thực sự ngắn, tôi có thể thấy trình tạo mã trình biên dịch, chẳng hạn như movb $65, 8(%esp); movb $66, 9(%esp); movb $0, 10(%esp)chuỗi "AB", nhưng phần lớn thời gian, nó sẽ nằm trong một phân đoạn không phải mã như .datahoặc .rodatatương tự (tùy thuộc vào việc mục tiêu có hỗ trợ hay không phân đoạn chỉ đọc).
Adam Rosenfield

Nếu chuỗi ký tự là hợp lệ trong toàn bộ thời lượng của chương trình, ngay cả trong quá trình phá hủy các đối tượng tĩnh thì nó có hợp lệ để trả về tham chiếu const cho một chuỗi ký tự không? Tại sao chương trình này hiển thị lỗi thời gian chạy, hãy xem ideone.com/FTs1Ig
Destructor

@AdamRosenfield: Nếu đôi khi bạn cảm thấy buồn chán, bạn có thể muốn xem (ví dụ) định dạng UNIX a.out kế thừa (ví dụ: freebsd.org/cgi/ đấm ). Một điều bạn nên nhanh chóng nhận thấy là nó chỉ hỗ trợ một phân đoạn dữ liệu, luôn luôn có thể ghi. Vì vậy, nếu bạn muốn chuỗi ký tự chỉ đọc, về cơ bản, nơi duy nhất họ có thể đến là đoạn văn bản (và vâng, tại thời điểm các trình liên kết thường làm chính xác điều đó).
Jerry Coffin

48

Tại sao tôi không nên cố gắng thay đổi nó?

Bởi vì đó là hành vi không xác định. Trích dẫn từ dự thảo C99 N1256 6.7.8 / 32 "Khởi tạo" :

VÍ DỤ 8: Tuyên bố

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

định nghĩa các đối tượng mảng char "đơn giản" stcó các phần tử được khởi tạo bằng chuỗi ký tự.

Tuyên bố này là giống hệt với

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

Nội dung của các mảng có thể sửa đổi. Mặt khác, tuyên bố

char *p = "abc";

định nghĩa pvới kiểu "con trỏ tới char" và khởi tạo nó để trỏ đến một đối tượng có kiểu "mảng char" có độ dài 4 có các phần tử được khởi tạo với một chuỗi ký tự bằng chữ. Nếu một nỗ lực được thực hiện để sử dụng pđể sửa đổi nội dung của mảng, hành vi không được xác định.

Họ đi đâu?

GCC 4.8 x86-64 ELF Ubuntu 14.04:

  • char s[]: cây rơm
  • char *s:
    • .rodata phần của tệp đối tượng
    • cùng phân đoạn nơi .textphần của tệp đối tượng bị kết xuất, có quyền Đọc và Exec, nhưng không ghi

Chương trình:

#include <stdio.h>

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

Biên dịch và dịch ngược:

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

Đầu ra chứa:

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

Vì vậy, chuỗi được lưu trữ trong .rodataphần.

Sau đó:

readelf -l a.out

Chứa (đơn giản hóa):

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

Điều này có nghĩa là tập lệnh liên kết mặc định bỏ cả hai .text.rodatavào một phân đoạn có thể được thực thi nhưng không được sửa đổi ( Flags = R E). Cố gắng sửa đổi một phân đoạn như vậy dẫn đến một segfault trong Linux.

Nếu chúng ta làm tương tự cho char[]:

 char s[] = "abc";

chúng tôi đạt được:

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

vì vậy nó được lưu trữ trong ngăn xếp (liên quan đến %rbp) và tất nhiên chúng ta có thể sửa đổi nó.


22

FYI, chỉ cần sao lưu các câu trả lời khác:

Tiêu chuẩn: ISO / IEC 14882: 2003 nói:

2,13. Chuỗi ký tự

  1. [...] Một chuỗi ký tự thông thường có kiểu Mảng của dòng n const charvà thời lượng lưu trữ tĩnh (3.7)

  2. Liệu tất cả các chuỗi ký tự là khác biệt (có nghĩa là, được lưu trữ trong các đối tượng không chồng lấp) được xác định theo thực hiện. Hiệu quả của việc cố gắng sửa đổi một chuỗi ký tự là không xác định.


2
Thông tin hữu ích, nhưng liên kết thông báo là dành cho C ++, trong khi câu hỏi được gửi đến c
Grijesh Chauhan

1
xác nhận số 2 trong 2.13. Với tùy chọn -Os (tối ưu hóa kích thước), gcc chồng lên các chuỗi ký tự trong .rodata.
Bành Trương

14

gcc làm cho một .rodataphần được ánh xạ "ở đâu đó" trong không gian địa chỉ và được đánh dấu chỉ đọc,

Visual C ++ ( cl.exe) tạo một .rdataphần cho cùng một mục đích.

Bạn có thể nhìn vào đầu ra từ dumpbinhoặc objdump(trên Linux) để xem các phần của tệp thực thi của bạn.

Ví dụ

>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
Tôi không thể thấy làm thế nào để có được sự tháo gỡ của phần thứ ba với objdump.
dùng2284570

@ user2284570, đó là vì phần đó không chứa lắp ráp. Nó chứa dữ liệu.
Alex Budovski

1
Chỉ là một vấn đề để có được đầu ra dễ đọc hơn. Ý tôi là tôi muốn có được các chuỗi nội tuyến với sự tháo gỡ thay vì địa chỉ cho các phần đó. (hem bạn biết printf("some null terminated static string");thay vì printf(*address);trong C)
user2284570

4

Nó phụ thuộc vào định dạng thực thi của bạn . Một cách để nghĩ về điều đó là nếu bạn đang lập trình lắp ráp, bạn có thể đặt chuỗi ký tự chuỗi trong phân đoạn dữ liệu của chương trình lắp ráp của bạn. Trình biên dịch C của bạn làm một cái gì đó tương tự, nhưng tất cả phụ thuộc vào hệ thống nhị phân của bạn đang được biên dịch cho hệ thống nào.


2

Chuỗi ký tự thường được phân bổ cho bộ nhớ chỉ đọc, làm cho chúng không thay đổi. Tuy nhiên, trong một số trình biên dịch có thể sửa đổi bằng "mẹo thông minh" .. Và mẹo thông minh là "sử dụng con trỏ ký tự trỏ vào bộ nhớ" .. hãy nhớ một số trình biên dịch, có thể không cho phép điều này..Đây là bản demo

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

0

Vì điều này có thể khác nhau từ trình biên dịch sang trình biên dịch, cách tốt nhất là lọc kết xuất đối tượng cho chuỗi tìm kiếm theo nghĩa đen:

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

trong đó các -slực lượng objdumpđể hiển thị toàn bộ nội dung của tất cả các phần, main.olà tệp đối tượng, cũng -B 1buộc grepphải in một dòng trước trận đấu (để bạn có thể thấy tên phần) và strlà chuỗi ký tự bạn đang tìm kiếm.

Với gcc trên máy Windows và một biến được khai báo mainnhư

char *c = "whatever";

đang chạy

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

trả lại

Contents of section .rdata:
 0000 77686174 65766572 00000000           whatever....
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.