Подтвердить что ты не робот

Существование объектов, созданных в C-функциях

Было установлено (см. ниже) размещение new требуется для создания объектов

int* p = (int*)malloc(sizeof(int));
*p = 42;  // illegal, there isn't an int

Однако это довольно стандартный способ создания объектов в C.

Вопрос в том, существует ли int, если он создан в C и возвращается в С++?

Иными словами, гарантируется ли законность? Предположим, что int одинаково для C и С++.

foo.h

#ifdef __cplusplus
extern "C" {
#endif

int* foo(void);

#ifdef __cplusplus
}
#endif

foo.c

#include "foo.h"
#include <stdlib.h>

int* foo(void) {
    return malloc(sizeof(int));
}

main.cpp

#include "foo.h"
#include<cstdlib>

int main() {
    int* p = foo();
    *p = 42;
    std::free(p);
}

Ссылки на обсуждения обязательного характера размещения new:

4b9b3361

Ответ 1

Да! Но только потому, что int является фундаментальным типом. Его инициализация является пустой работой:

[dcl.init]/7:

Для инициализации объекта типа T по умолчанию:

  • Если T является классом класса (возможно, cv-qualit), рассматриваются конструкторы. Соответствующие конструкторы перечислены ([over.match.ctor]), и выбирается лучший для инициализатора() через разрешение перегрузки. Выбранный таким образом конструктор называется, с пустым списком аргументов, чтобы инициализировать объект.

  • Если T - тип массива, каждый элемент инициализируется по умолчанию.

  • В противном случае инициализация не выполняется.

Акцент мой. Так как "не инициализация" int сродни инициализации по умолчанию, это время жизни начинается после выделения хранилища:

[basic.life]/1:

Время жизни объекта или ссылки - это свойство времени выполнения объекта или ссылки. Говорят, что объект имеет беспустую инициализация, если она относится к классу или совокупности, и она или одна из его подобъекты инициализируются конструктором, отличным от тривиального конструктор по умолчанию. Время жизни объекта типа T начинается, когда:

  • сохраняется хранилище с надлежащим выравниванием и размером для типа T и
  • Если объект имеет незапамятную инициализацию, его инициализация завершена,

Распределение памяти может быть выполнено любым способом, приемлемым стандартом С++. Да, даже просто называя malloc. Компиляция кода C с компилятором С++ в противном случае была бы очень плохой идеей. И тем не менее, часто задаваемые вопросы по С++ в течение нескольких лет.


Кроме того, поскольку стандарт С++ отсылает к C стандарт, где malloc. Я считаю, что формулировка должна быть сформулирована также. И вот он:

7.22.3.4 Функция malloc - Параграф 2:

Функция malloc выделяет пространство для объекта, размер которого равен заданный по размеру и значение которого неопределенно.

"значение неопределенно", часть вида указывает там объект. В противном случае, как это могло бы иметь значение, не говоря уже о неопределенном?

Ответ 2

Я думаю, что этот вопрос плохо поставлен. В С++ мы имеем только понятия единиц перевода и привязки, последнее просто означает, при каких обстоятельствах имена, объявленные в разных ТУ, относятся к одному и тому же объекту или нет.

Ничего практически не говорится о процессе связывания как такового, правильность которого должна быть гарантирована компилятором/компоновщиком; даже если приведенные выше фрагменты кода были чисто источниками С++ (с заменой malloc на новый новый int), результат будет по-прежнему реализован (например, рассмотрите файлы объектов, скомпилированные с несовместимыми параметрами компилятора /ABIs/runtimes ).

Итак, либо мы говорим в полной общности, и делаем вывод, что любая программа из более чем одного ТУ потенциально неверна или мы должны считать само собой разумеющимся, что процесс связывания "действителен" (только реализация знает) и, следовательно, воспринимается как должное что если функция из некоторого исходного языка (в данном случае C) примаризуется, чтобы вернуть "указатель на существующий int", то одна и та же функция на целевом языке (С++) должна по-прежнему быть "указателем на существующий int" (в противном случае, следуя [dcl.link], мы не могли сказать, что связь была "достигнута", возвращаясь на землю без человека).

Итак, на мой взгляд, реальной проблемой является оценка того, что "существующий" int находится на C и С++, сравнительно. Когда я читаю соответствующие стандарты, на обоих языках время жизни int в основном начинается, когда его хранилище зарезервировано для него: в случае OP выделенного (в C)/динамическом (в С++) объекте продолжительности хранения это происходит (на стороне C), когда эффективный тип lvalue * pointer_to_int становится int (например, когда ему присваивается значение, до тех пор, not-yet-an-int может ловить (*)).

Это не происходит в случае OP, результат malloc пока не имеет эффективного типа. Итак, что int не существует ни в C, ни в С++, это просто переинтерпретированный указатель.

Тем не менее, С++ часть кода OP присваивается сразу после возвращения из foo(); если бы это было предназначено, то можно было бы сказать, что, учитывая, что malloc() в С++ требуется с семантикой C, было бы достаточно разместить новое место на стороне С++, чтобы сделать его действительным (как показывают предоставленные ссылки).

Итак, суммируя, либо код C должен быть исправлен, чтобы вернуть указатель на существующий int (путем присвоения ему), либо код С++ должен быть исправлен путем добавления места размещения new. (извините за длительные споры...:))


(*) здесь я не утверждаю, что единственной проблемой является наличие ловушечного представления; если это так, можно утверждать, что результат foo() является неопределенным значением на стороне С++, а значит, что-то, на что вы можете смело назначить. Ясно, что это не так, потому что есть также правила сглаживания, которые нужно учитывать...

Ответ 3

Я могу выделить две части этого вопроса, которые следует рассматривать отдельно.


Время жизни объекта

Было установлено (см. ниже) размещение нового требуется для создания объектов

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

Единственные люди, которые должны быть заинтересованы в том, что на самом деле говорит сломанная часть стандарта, - это люди, ответственные за исправление поломки. Другие люди (языковые и языковые разработчики) должны отложить до существующей практики и здравого смысла. Оба из них говорят, что для создания int, malloc не требуется new.

Этот документ идентифицирует проблему и предлагает исправление (спасибо @T.C. для ссылки)


Совместимость с C

Предположим, что int одинаково для C и С++

Недостаточно предположить, что.

Также необходимо предположить, что int* - то же самое, что одна и та же память доступна для функций C и С++, связанных между собой в программе, и что реализация С++ не определяет семантику вызовов функций, написанных в C, чтобы вытирать жесткий диск и красть свою подругу. Другими словами, реализации C и С++ достаточно совместимы.

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

Единственное, что говорится в стандарте: "Каждая реализация должна обеспечивать связь с функциями, написанными на языке программирования C" (dcl.link). Какова семантика такой привязки остается undefined.

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

Ответ 4

Вопрос бессмыслен. Извините. Это единственный ответ "адвокат".

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

Ничто в любом языке не описывается в терминах реализации на низком уровне (что смешно для языков, которые часто описываются как "сборка высокого уровня" ). Оба C и С++ указаны (если вы можете назвать эту спецификацию) на очень абстрактном уровне, а высокий и низкий уровни никогда не будут повторно подключены. Это порождает бесконечные дискуссии о том, что поведение undefined означает на практике, как работают профсоюзы и т.д.

Ответ 5

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

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

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

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

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

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

Ответ 6

Нет, int не существует, как описано в связанном Q/As. Важная стандартная цитата читается в С++ 14:

1.8 Объектная модель С++ [intro.object]
[...] Объект создается определением (3.1), новым выражением (5.3.4) или при необходимости (12.2). [...]

(12.2 - абзац о временных объектах)

В стандарте С++ нет правил для взаимодействия кода C и С++. Компилятор С++ может анализировать только объекты, созданные кодом С++, но не переданные ему некоторые биты образуют внешний источник, такой как программа на C, или сетевой интерфейс и т.д.

Многие правила предназначены для оптимизации. Некоторые из них возможны только в том случае, если компилятор не должен предполагать, что неинициализированная память содержит действительные объекты. Например, правило, что нельзя прочитать неинициализированный int, не имеет смысла иначе, потому что если int может существовать где угодно, почему было бы незаконным читать неопределенное значение int?

Это будет стандартный способ записи программы:

int main() {
    void* p = foo();
    int i = 42;
    memcpy(p, &i, sizeof(int));
    //std::free(p);   //this works only if C and C++ use the same heap.
}