Строковые литералы: куда они идут? - PullRequest
147 голосов
/ 07 апреля 2010

Меня интересует, где располагаются / хранятся строковые литералы.

Я нашел один интригующий ответ здесь , говорящий:

Определение строки inline фактически встраивает данные в саму программу и не может быть изменено (некоторые компиляторы допускают это умным трюком, не беспокойтесь).

Но это было связано с C ++, не говоря уже о том, что в нем сказано не беспокоить.

Я беспокоюсь. = D

Итак, мой вопрос: где и как хранится мой строковый литерал? Почему я не должен пытаться изменить это? Зависит ли реализация от платформы? Кто-нибудь хочет уточнить «умный трюк»?

Ответы [ 8 ]

117 голосов
/ 07 апреля 2010

Обычный метод для размещения строковых литералов в разделе «только для чтения», который отображается в пространство процесса как «только для чтения» (поэтому его нельзя изменить).

Это зависит от платформы. Например, более простые архитектуры микросхем могут не поддерживать сегменты памяти только для чтения, поэтому сегмент данных будет доступен для записи.

Вместо того, чтобы попытаться найти способ сделать строковые литералы изменяемыми (это будет сильно зависеть от вашей платформы и может измениться со временем), просто используйте массивы:

char foo[] = "...";

Компилятор организует инициализацию массива из литерала, и вы можете изменить массив.

47 голосов
/ 07 апреля 2010

На этот вопрос нет единого ответа. Стандарты C и C ++ просто говорят, что строковые литералы имеют статическую продолжительность хранения, любая попытка их изменения приводит к неопределенному поведению, а несколько строковых литералов с одинаковым содержимым могут или не могут совместно использовать одно и то же хранилище.

В зависимости от системы, для которой вы пишете, и возможностей используемого формата исполняемого файла, они могут храниться вместе с программным кодом в текстовом сегменте или иметь отдельный сегмент для инициализированных данных.

Определение деталей также будет зависеть от платформы - скорее всего, есть инструменты, которые могут подсказать вам, где она находится. Некоторые даже дадут вам контроль над такими деталями, если вы этого захотите (например, gnu ld позволяет вам предоставить скрипт, который расскажет все о том, как группировать данные, код и т. Д.)

39 голосов

Почему бы мне не попытаться изменить его?

Потому что это неопределенное поведение.Цитата C99 N1256 черновик 6.7.8 / 32 "Инициализация" :

ПРИМЕР 8: Объявление

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

определяет"обычные" объекты массива char s и t, элементы которых инициализируются литералами символьных строк.

Это объявление идентично

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

Содержимое массивов может изменяться.С другой стороны, объявление

char *p = "abc";

определяет p с типом «pointer to char» и инициализирует его, чтобы указывать на объект с типом «array of char» длиной 4, элементы которого инициализируются ссимвольная строка литерала.Если предпринята попытка использовать p для изменения содержимого массива, поведение не определено.

Куда они идут?

GCC4.8 x86-64 ELF Ubuntu 14.04:

  • char s[]: стек
  • char *s:
    • .rodata секция объектного файла
    • тот же сегмент, куда выводится раздел .text объектного файла, который имеет разрешения на чтение и выполнение, но не на запись

Программа:

#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).Попытка изменить такой сегмент приводит к ошибке в Linux.

Если мы сделаем то же самое для char[]:

 char s[] = "abc";

, то получим:

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

поэтому он сохраняется в стеке (относительно %rbp), и мы, конечно, можем его изменить.

21 голосов
/ 26 мая 2011

К вашему сведению, просто резервное копирование других ответов:

Стандарт: ИСО / МЭК 14882: 2003 говорит:

2,13. Строковые литералы

  1. [...] Обычный строковый литерал имеет тип «массив из n const char» и статическая продолжительность хранения (3,7)

  2. Все ли строковые литералы различны (то есть, хранятся в неперекрывающиеся объекты) реализация определяется. Эффект пытаясь изменить строковый литерал не определено.

13 голосов
/ 07 апреля 2010

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
4 голосов
/ 07 апреля 2010

Это зависит от формата вашего исполняемого файла . Можно подумать об этом, если бы вы программировали на ассемблере, вы могли бы поместить строковые литералы в сегмент данных вашей программы на ассемблере. Ваш компилятор C делает что-то подобное, но все зависит от того, для какой системы вы используете двоичный файл.

2 голосов
/ 13 октября 2013

Строковые литералы часто выделяются только для чтения, что делает их неизменяемыми. Тем не менее, в некоторых компиляторах модификация возможна с помощью «умного трюка». А умный трюк заключается в «использовании указателя символов, указывающего на память» ... помните, что некоторые компиляторы могут этого не допустить .. Вот демо

char *tabHeader = "Sound";
*tabHeader = 'L';
printf("%s\n",tabHeader); // Displays "Lound"
0 голосов
/ 10 ноября 2015

Поскольку это может отличаться от компилятора к компилятору, лучший способ - отфильтровать дамп объекта для найденного строкового литерала:

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....
...