c++ - parametros - Existencia de objetos creados en funciones C.
funciones y procedimientos en c++ (6)
¡Sí! Pero solo porque int
es un tipo fundamental. Su inicialización es la operación vacua:
Para inicializar por defecto un objeto de tipo T significa:
Si T es un tipo de clase (posiblemente cv-calificado), se consideran constructores. Los constructores correspondientes se enumeran ([over.match.ctor]), y el mejor para el inicializador () se elige mediante resolución de sobrecarga. El constructor así seleccionado se llama, con una lista de argumentos vacía, para inicializar el objeto.
Si T es un tipo de matriz, cada elemento se inicializa por defecto.
De lo contrario, no se realiza ninguna inicialización.
Énfasis mío. Dado que "no inicializar" un int
es similar a su inicialización predeterminada, su vida útil comienza una vez que se asigna el almacenamiento:
La vida útil de un objeto o referencia es una propiedad de tiempo de ejecución del objeto o referencia. Se dice que un objeto tiene una inicialización no vacía si es de una clase o un tipo agregado y uno de sus subobjetos es iniciado por un constructor que no sea un constructor predeterminado trivial. La vida útil de un objeto de tipo T comienza cuando:
- se obtiene un almacenamiento con la alineación y tamaño adecuados para el tipo T, y
- si el objeto tiene una inicialización no vacía, su inicialización es completa,
La asignación de almacenamiento se puede hacer de cualquier manera aceptable por el estándar C ++. Sí, incluso acaba de llamar a malloc
. Compilar el código C con un compilador de C ++ sería una muy mala idea de lo contrario. Y, sin embargo, las preguntas frecuentes de C ++ lo han sugerido durante años .
Además, dado que el estándar de C ++ difiere al estándar de C en lo que respecta a malloc
. Pienso que la redacción también debería aparecer. Y aquí está:
7.22.3.4 La función malloc - Párrafo 2 :
La función malloc asigna espacio para un objeto cuyo tamaño se especifica por tamaño y cuyo valor es indeterminado.
La parte "el valor es indeterminado" indica que hay un objeto allí. De lo contrario, ¿cómo podría tener algún valor, por no hablar de uno indeterminado?
Se ha establecido (ver más abajo) Se requiere una new
ubicación para crear objetos.
int* p = (int*)malloc(sizeof(int));
*p = 42; // illegal, there isn''t an int
Sin embargo, esa es una forma bastante estándar de crear objetos en C.
La pregunta es, ¿existe el int
si se crea en C y se devuelve a C ++?
En otras palabras, ¿se garantiza que lo siguiente es legal? Supongamos que int
es el mismo para C y 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);
}
Enlaces a las discusiones sobre la naturaleza obligatoria de la colocación new
:
- ¿Es legalmente necesaria una nueva ubicación para colocar un int en una matriz char?
- https://stackoverflow.com/a/46841038/4832499
- https://groups.google.com/a/isocpp.org/forum/#!msg/std-discussion/rt2ivJnc4hg/Lr541AYgCQAJ
- https://www.reddit.com/r/cpp/comments/5fk3wn/undefined_behavior_with_reinterpret_cast/dal28n0/
- reinterpret_cast creando un objeto construible por defecto trivialmente
Aunque ni el estándar C ni, por lo que sé, el estándar C ++ reconoce oficialmente el concepto, casi todas las plataformas que permiten que los programas producidos por diferentes compiladores se vinculen entre sí soportarán funciones opacas .
Al procesar una llamada a una función opaca, un compilador comenzará asegurándose de que el valor de todos los objetos que podrían ser legítimamente examinados por un código externo se escriba en el almacenamiento asociado con esos objetos. Una vez hecho esto, colocará los argumentos de la función en los lugares especificados por la documentación de la plataforma (el ABI o la interfaz binaria de la aplicación) y realizará la llamada.
Una vez que se devuelve la función, el compilador asumirá que cualquier objeto que una función externa podría haber escrito, puede haberse escrito y, por lo tanto, volverá a cargar cualquiera de esos valores del almacenamiento asociado con esos objetos la próxima vez que se utilicen.
Si el almacenamiento asociado con un objeto contiene un patrón de bits particular cuando se devuelve una función opaca, y si el objeto mantendría ese patrón de bits cuando tiene un valor definido, entonces un compilador debe comportarse como si el objeto tuviera ese valor definido sin tener en cuenta cómo llegó a mantener ese patrón de bits.
El concepto de funciones opacas es muy útil, y no veo ninguna razón por la que los estándares C y C ++ no deban reconocerlo, ni proporcionar una función opaca estándar de "no hacer nada". Sin duda, llamar innecesariamente a las funciones opacas impedirá enormemente lo que podrían ser optimizaciones útiles, pero ser capaz de forzar a un compilador a tratar las acciones como llamadas de función opacas cuando sea necesario puede hacer posible habilitar más optimizaciones en otros lugares.
Desafortunadamente, las cosas parecen ir en la dirección opuesta, y los sistemas de compilación intentan aplicar cada vez más la optimización de "todo el programa". WPO sería bueno si hubiera una forma de distinguir entre llamadas a funciones que fueran opacas porque se necesitaba la "barrera de optimización" completa, de aquellas que habían sido tratadas como opacas simplemente porque no había manera de que los optimizadores "vean" a través de Límites del módulo. A menos o hasta que se agreguen las barreras adecuadas, no conozco ninguna forma de garantizar que los optimizadores no se vuelvan "inteligentes" de manera que se rompa el código que hubiera tenido un comportamiento definido con las barreras en su lugar.
Creo que la pregunta está mal planteada. En C ++ solo tenemos los conceptos de unidades de traducción y enlace, este último simplemente significa bajo qué circunstancias los nombres declarados en diferentes TU se refieren a la misma entidad o no.
No se dice prácticamente nada sobre el proceso de vinculación como tal, cuya corrección debe ser garantizada por el compilador / vinculador de todos modos; incluso si los fragmentos de código anteriores fueran fuentes puramente C ++ (con malloc reemplazado por un nuevo int), el resultado todavía estaría definido por la implementación (por ejemplo, considere los archivos de objetos compilados con opciones de compilador incompatibles / ABIs / runtimes).
Entonces, o bien hablamos en general y concluimos que cualquier programa hecho de más de una TU es potencialmente incorrecto o debemos dar por sentado que el proceso de vinculación es ''válido'' (solo lo sabe la implementación) y, por lo tanto, damos por sentado que si un la función de algún idioma de origen (en este caso C) se inicia para devolver un ''puntero a un int existente'', entonces la misma función en el idioma de destino (C ++) aún debe ser un ''puntero a un int existente'' (de lo contrario, sigue a [ dcl.link], no podríamos decir que el vínculo se ha "logrado", volviendo a la tierra de nadie ).
Entonces, en mi opinión, el problema real es evaluar lo que es una int "existente" en C y C ++, comparativamente. A medida que leo los estándares correspondientes, en ambos idiomas una vida útil int comienza básicamente cuando se reserva su almacenamiento: en el caso OP de un objeto de duración de almacenamiento asignado (en C) / dinámico (en c ++), esto ocurre (en el lado C) ) cuando el tipo efectivo de lvalue * pointer_to_int se convierte en int (p. ej., cuando se le asigna un valor; hasta ese momento, el comando "no-an-int-int" (*)).
Esto no sucede en el caso de OP, el resultado de malloc aún no tiene un tipo efectivo. Entonces, ese int no existe ni en C ni en C ++, es solo un puntero reinterpretado.
Dicho esto, la parte c ++ del código OP se asigna justo después de regresar de foo (); si se pretendiera, entonces podríamos decir que dado que se requiere malloc () en C ++ con semántica de C, una ubicación nueva en el lado de c ++ sería suficiente para que sea válida (como lo muestran los enlaces provistos).
Por lo tanto, resumiendo, el código C debe fijarse para devolver un puntero a un int existente (asignándolo) o el código c ++ debe solucionarse agregando una ubicación nueva. (perdón por la larga discusión ... :))
(*) aquí no estoy afirmando que el único problema sea la existencia de la representación de trampas; si lo fuera, se podría argumentar que el resultado de foo () es un valor indeterminado en el lado C ++, por lo tanto, es algo que puede asignar de forma segura. Claramente este no es el caso porque también hay reglas de alias para tener en cuenta ... No, el int
no existe, como se explica en el enlace Q / As. Una cita estándar importante se lee así en C ++ 14:
1.8 El modelo de objetos C ++ [intro.object]
[...] Un objeto es creado por una definición (3.1), por una nueva expresión (5.3.4) o por la implementación (12.2) cuando sea necesario. [...]
(12.2 es un párrafo sobre objetos temporales)
El estándar de C ++ no tiene reglas para la interconexión de los códigos C y C ++. Un compilador de C ++ solo puede analizar los objetos creados por el código de C ++, pero no algunos bits pasados a él forman una fuente externa como un programa C, una interfaz de red, etc.
Muchas reglas se adaptan para hacer posibles las optimizaciones. Algunos de ellos solo son posibles si el compilador no tiene que asumir que la memoria sin inicializar contiene objetos válidos. Por ejemplo, la regla de que uno no puede leer un int
inicializar no tendría sentido de otra manera, porque si los int
s existen en cualquier lugar, ¿por qué sería ilegal leer un valor de int
indeterminado?
Esta sería una forma estándar compatible para escribir el programa:
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.
}
Puedo identificar dos partes de esta pregunta que deben abordarse por separado.
Vida útil del objeto
Se ha establecido (ver más abajo) Se requiere una nueva ubicación para crear objetos.
Estoy convencido de que esta área de la norma contiene ambigüedad, omisión, contradicción y / o incompatibilidad gratuita con la práctica existente, y por lo tanto, debe considerarse rota .
Las únicas personas que deberían estar interesadas en lo que en realidad dice una parte rota de la norma son las personas responsables de arreglar la rotura. Otras personas (usuarios de idiomas e implementadores de idiomas por igual) deben someterse a la práctica existente y al sentido común. Ambos dicen que uno no necesita new
para crear un int
, basta con malloc
.
Este documento identifica el problema y propone una solución (gracias a @TC por el enlace)
Compatibilidad C
Supongamos que int es el mismo para C y C ++
No es suficiente asumir eso.
También se debe asumir que int*
es el mismo, que las funciones C y C ++ vinculadas en un programa tienen acceso a la misma memoria, y que la implementación de C ++ no define la semántica de las llamadas a las funciones escritas en el lenguaje de programación C para Estar limpiando tu disco duro y robando a tu novia. En otras palabras, que las implementaciones de C y C ++ son suficientemente compatibles .
Nada de esto está estipulado por el estándar o debe suponerse. De hecho, hay implementaciones de C que son incompatibles entre sí, por lo que ambas no pueden ser compatibles con la misma implementación de C ++.
Lo único que dice el estándar es "Cada implementación debe proporcionar un enlace a las funciones escritas en el lenguaje de programación C" (dcl.link). Lo que es la semántica de dicho enlace se deja sin definir.
Aquí, como antes, el mejor curso de acción es diferir a la práctica existente y al sentido común. Ambos dicen que una implementación de C ++ por lo general viene con una implementación de C suficientemente compatible , con el enlace funcionando como uno esperaría.
La pregunta no tiene sentido. Lo siento. Esta es la única respuesta de "abogado" posible.
No tiene sentido porque el lenguaje C ++ y C se ignoran entre sí, ya que ignoran cualquier otra cosa.
Nada en ninguno de los lenguajes se describe en términos de implementación de bajo nivel (lo cual es ridículo para los lenguajes a menudo descritos como "ensamblaje de alto nivel"). Tanto C como C ++ se especifican (si se puede llamar a eso una especificación) a un nivel muy abstracto, y los niveles alto y bajo nunca se vuelven a conectar. Esto genera debates interminables sobre lo que significa en la práctica comportamientos indefinidos, cómo funcionan los sindicatos, etc.