Строковые литералы: куда они идут?



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



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




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




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



я беспокою. =D



Так что мой вопрос в том, где и как хранится мой строковый литерал? Почему бы мне не попытаться изменить его? Реализация зависит от платформы? Кто-нибудь хочет уточнить "умный трюк"?"

650   8  

8 ответов:

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

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

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

char foo[] = "...";

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

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

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

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

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

потому что это неопределенное поведение. Цитата из С99 проект N12566.7.8/32 "инициализация":

пример 8: объявление

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

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

эта декларация идентична

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

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

char *p = "abc";

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

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

GCC 4.8 x86-64 Elf Ubuntu 14.04:

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

программа:

#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   x0,-0x8(%rbp)
f:  00 
        c: R_X86_64_32S .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   x636261,-0x10(%rbp)

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

FYI, просто резервное копирование других ответов:

стандартные: ISO / IEC 14882: 2003 говорит:

2.13. Строковые литералы

  1. [...]Обычный строковый литерал имеет тип "массив n const char" и статическую длительность хранения (3.7)

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

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

зависит от

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

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

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

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

Comments

    Ничего не найдено.