Как компиляторы C ++ объединяют идентичные строковые литералы - PullRequest
15 голосов
/ 08 июня 2011

Как компилятор (MS Visual C ++ 2010) объединяет одинаковые строковые литералы в разных исходных файлах cpp?Например, если у меня есть строковый литерал "hello world \ n" в src1.cpp и src2.cpp соответственно.Скомпилированный exe-файл будет иметь только 1 строковый литерал "hello world", вероятно, в разделе constant / readonly.Выполняется ли эта задача компоновщиком?

Чего я надеюсь достичь, так это того, что я получил несколько модулей, написанных на ассемблере, которые будут использоваться модулями C ++.И эти сборочные модули содержат много длинных строковых литеральных определений.Я знаю, что строковые литералы идентичны некоторым другим строковым литералам в источнике C ++.Если я свяжу мой obj-код, сгенерированный сборкой, с obj-кодом, сгенерированным компилятором, будет ли этот компоновщик объединять эти строковые литералы для удаления избыточных строк, как в случае, когда все модули находятся в C ++?

Ответы [ 6 ]

9 голосов
/ 08 июня 2011

(обратите внимание, что следующее относится только к MSVC)

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

Однако это была ошибка. Оказывается, что компоновщик не имеет особого участия в слиянии строковых литералов - что происходит, когда компилятору задается опция /GF, он помещает строковые литералы в раздел «COMDAT» объектного файла с именем объекта на основе на содержимое строкового литерала. Таким образом, флаг /GF необходим для шага compile , а не для шага ссылки.

Когда вы используете опцию /GF, компилятор помещает каждый строковый литерал в объектном файле в отдельный раздел как объект COMDAT. Различные объекты COMDAT с одинаковыми именами будут свернуты компоновщиком (я не совсем уверен в семантике COMDAT или в том, что может делать компоновщик, если объекты с одинаковыми именами имеют разные данные). Таким образом, файл C, который содержит

char* another_string = "this is a string";

Будет иметь что-то вроде следующего в объектном файле:

SECTION HEADER #3
  .rdata name
       0 physical address
       0 virtual address
      11 size of raw data
     147 file pointer to raw data (00000147 to 00000157)
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
40301040 flags
         Initialized Data
         COMDAT; sym= "`string'" (??_C@_0BB@LFDAHJNG@this?5is?5a?5string?$AA@)
         4 byte align
         Read Only

RAW DATA #3
  00000000: 74 68 69 73 20 69 73 20 61 20 73 74 72 69 6E 67  this is a string
  00000010: 00      

с таблицей перемещений, соединяющей имя переменной another_string1 с литеральными данными.

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

В любом случае, если вы хотите, чтобы литералы в файле сборки обрабатывались одинаковым образом, вам необходимо организовать размещение литералов в объектном файле таким же образом. Честно говоря, я не знаю, какой (если вообще есть) механизм для этого может быть у ассемблера. Поместить объект в раздел «COMDAT», вероятно, довольно легко - получение имени объекта, основанного на содержимом строки (и искажение соответствующим образом), - это другая история.

Если не существует какой-либо директивы / ключевого слова сборки, которое конкретно поддерживает этот сценарий, я думаю, вам может не повезти Конечно, может быть один, но я достаточно ржавый с ml.exe, чтобы не знать, и быстрый взгляд на скудные документы MSDN для ml.exe ничего не выпрыгнул.

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

4 голосов
/ 08 июня 2011

Да, процесс объединения ресурсов выполняется компоновщиком.

Если ваши ресурсы в скомпилированном коде сборки правильно помечены как ресурсы, компоновщик сможет объединить их со скомпилированным кодом C.

3 голосов
/ 08 июня 2011

Многое может зависеть от конкретного компилятора, компоновщика и способа их управления. Например, этот код:

// s.c
#include <stdio.h>

void f();

int main() {
    printf( "%p\n", "foo" );
    printf( "%p\n", "foo" );
    f();
}

// s2.c
#include <stdio.h>

void f() {
    printf( "%p\n", "foo" );
    printf( "%p\n", "foo" );
}

при компиляции как:

gcc s.c s2.c

производит:

00403024
00403024
0040302C
0040302C

, из которого видно, что строки были объединены только в отдельных единицах перевода.

1 голос
/ 08 июня 2011

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

Таким образом, вы почти наверняка захотите определить строки в коде ассемблера с помощьюимена.Чтобы использовать их из C или C ++, вы хотите поместить объявление extern массива в заголовок, который вы можете #include в любых файлах, к которым требуется доступ (и в вашем коде C ++ вы будете использовать имена, а несами литералы):

foo.asm

.model flat, c

.data
    string1 db "This is the first string", 10, 0
    string2 db "This is the second string\n", 10, 0

foo.h:

extern char string1[];
extern char string2[];

bar.cpp

#include "foo.h"

void baz() { std:::cout << string1; }
1 голос
/ 08 июня 2011

"/ GF (исключить повторяющиеся строки)"

http://msdn.microsoft.com/en-us/library/s0s0asdt.aspx

1 голос
/ 08 июня 2011

Одинаковые литералы в пределах одной и той же единицы перевода обрабатываются на этапе синтаксического анализа. Компилятор преобразует литералы в токены и сохраняет их в таблице (для простоты предположим [идентификатор токена, значение]). Когда компилятор встречает литерал в первый раз, значение вводится в таблицу. В следующих встречах используется тот же литерал. При создании кода это значение помещается в память, а затем каждый доступ считывает это единственное значение (за исключением тех случаев, когда размещение значения в исполняемом коде более одного раза ускоряет выполнение или сокращает длину исполняемого файла).

Дублирующие литералы в нескольких единицах перевода могут быть объединены компоновщиком. Все идентификаторы, помеченные с глобальным доступом (то есть видимые извне единицы перевода), будут объединены, если это возможно. Это означает, что код будет иметь доступ только к версии символа.

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

Имейте в виду, что с некоторыми компиляторами и компоновщиками они могут не выполнять консолидацию по умолчанию. Некоторым может потребоваться переключатель командной строки (или опция). Проверьте документацию вашего компилятора, чтобы увидеть, как он обрабатывает дубликаты идентификаторов или текстовые строки.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...