Уместно ли установить значение const char * в заголовочном файле

Я видел людей, использующих 2 метода для объявления и определения char *,

Medhod 1: заголовочный файл имеет ниже

extern const char* COUNTRY_NAME_USA = "USA";

Medhod 2:
Заголовочный файл имеет следующую декларацию:

extern const char* COUNTRY_NAME_USA;

Файл cpp имеет следующее определение:

extern const char* COUNTRY_NAME_USA = "USA";
  1. Является ли метод 1 неправильным в некотором роде?
  2. Какая разница между двумя?
  3. Я понимаю разницу междуconst char * const var", а также "const char * var". Если в вышеуказанных методах, если"const char * const var"объявлено и определено в заголовке, как в методе 1, будет ли это иметь смысл?

3 ответа

Решение

Первый метод действительно неверен, так как он дает определение объекта COUNTRY_NAME_USA с внешней связью в заголовочном файле. Как только этот заголовочный файл включается в несколько единиц перевода, правило одного определения (ODR) нарушается. Код не будет скомпилирован (точнее, он не сможет связать).

Второй метод является правильным. Ключевое слово extern является необязательным в определении, то есть в файле cpp вы можете просто сделать

const char* COUNTRY_NAME_USA = "USA"

предполагая, что объявление из заголовочного файла предшествует этому определению в этом модуле перевода.

Кроме того, я предполагаю, что, поскольку имя объекта написано с большой буквы, оно, вероятно, должно быть константой. Это так, тогда он должен быть объявлен / определен как const char* const COUNTRY_NAME_USA (обратите внимание на дополнительные const).

Наконец, принимая во внимание эту последнюю деталь, вы можете просто определить свою константу как

const char* const COUNTRY_NAME_USA = "USA"; // no `extern`!

в заголовочном файле. Поскольку теперь он является константой, по умолчанию он имеет внутреннюю связь, что означает отсутствие нарушения ODR, даже если заголовочный файл включен в несколько блоков перевода. В этом случае вы получаете отдельный COUNTRY_NAME_USA lvalue в каждой единице перевода (в то время как в extern метод вы получаете один для всей программы). Только вы знаете, что вам нужно в вашем случае.

В чем смысл?

Если вы хотите искать строки (которые могут быть локализованы), это будет лучше всего:

namespace CountryNames {
    const char* const US = "USA";
};

Поскольку указатель является const, он теперь имеет внутреннюю связь и не будет вызывать несколько определений. Большинство компоновщиков также объединяют избыточные константы, поэтому вы не потеряете место в исполняемом файле.

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

Если у вас должны быть глобальные переменные, обычной практикой является объявление их в файле.h и определение их в одном (и только одном) файле.cpp.

В.h файле;

extern int x;

В файле.cpp;

int x=3;

Я использовал int (возможно, самый фундаментальный базовый тип?), А не const char *, как в вашем примере, потому что суть вашей проблемы не зависит от типа переменной.

Основная идея заключается в том, что вы можете объявлять переменную несколько раз, поэтому каждый файл.cpp, содержащий файл.h, объявляет переменную, и это нормально. Но вы определяете это только один раз. Определение - это оператор, в котором вы назначаете начальное значение переменной (с =). Вам не нужны определения в файлах.h, потому что тогда, если файл.h включен в несколько файлов.cpp, вы получите несколько определений. Если у вас есть несколько определений одной переменной, существует проблема во время компоновки, потому что компоновщик хочет назначить адрес переменной и не может разумно сделать это, если существует несколько ее копий.

Дополнительная информация добавлена ​​позже, чтобы попытаться облегчить путаницу Суда;

Постарайтесь свести свою проблему к ее минимальным частям, чтобы лучше ее понять;

Представьте, что у вас есть программа, которая содержит три файла.cpp. Для сборки программы каждый.cpp компилируется отдельно для создания трех объектных файлов, затем три объектных файла связываются вместе. Если три файла.cpp следующие (пример A, хорошая организация);

file1.cpp

extern int x;

file2.cpp

extern int x;

file3.cpp

extern int x;

Затем файлы будут скомпилированы и скомпонованы без проблем (по крайней мере, в отношении переменной x). Нет проблем, потому что каждый файл объявляет только переменную x. Объявление просто говорит о том, что где-то есть переменная, которую я могу (или не могу) использовать.

Лучший способ добиться того же - следующий (пример А, лучшая организация);

header.h

extern int x;

file1.cpp

#include "header.h"

file2.cpp

#include "header.h"

file3.cpp

#include "header.h"

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

Теперь рассмотрим другой рабочий пример (пример B, хорошая организация);

file1.cpp

extern int x;

file2.cpp

extern int x;

file3.cpp

extern int x;
int x=3;

Это будет хорошо работать, как хорошо. Все три файла.cpp объявляют x и один фактически определяет его. Мы могли бы пойти дальше и добавить больше кода в функции в любой из трех файлов, которые манипулируют переменной x, и мы не получили бы никаких ошибок. Опять же, мы должны использовать заголовочный файл, чтобы объявление входило только в один физический файл (пример B, лучшая организация).

header.h

extern int x;

file1.cpp

#include "header.h"

file2.cpp

#include "header.h"

file3.cpp

#include "header.h"
int x=3;

Наконец, рассмотрим пример, который просто не работает (пример C, не работает);

file1.cpp

int x=3;

file2.cpp

int x=3;

file3.cpp

int x=3;

Каждый файл будет компилироваться без проблем. Проблема возникает во время соединения, потому что теперь мы определили три отдельные переменные int x. Они имеют одно и то же имя и являются глобально видимыми. Задача компоновщика - собрать все объекты, необходимые для одной программы, в один исполняемый файл. Глобально видимые объекты должны иметь уникальное имя, чтобы компоновщик мог поместить одну копию объекта по одному определенному адресу (месту) в исполняемом файле и разрешить всем остальным объектам доступ к нему по этому адресу. В этом случае компоновщик не может выполнить свою работу с глобальной переменной x, поэтому вместо этого он заблокирует ошибку.

Кроме того, предоставление разных определений различных начальных значений не решает проблему. Добавление к каждому определению ключевого слова static решает проблему, поскольку теперь переменные не видны глобально, а скорее видны в файле.cpp, в котором они определены.

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

header.h

int x=3;  // Don't put this in a .h file, causes multiple definition link error

file1.cpp

#include "header.h"

file2.cpp

#include "header.h"

file3.cpp

#include "header.h"

Фу, я надеюсь, что кто-то прочитает это и получит некоторую выгоду от этого. Иногда спрашивающий просит простое объяснение в терминах основных понятий, а не объяснение продвинутого ученого.

Другие вопросы по тегам