2010-09-14 8 views
14

me parece que en el siguiente fragmento de códigocambiando el valor const en C

const int i = 2; 
const int* ptr1= &i; 
int* ptr2 = (int*)ptr1; 
*ptr2 =3; 

i 's valor cambia a 3. Lo que pude gustaría saber es por qué es esto permitió. ¿Cuáles son las situaciones en las que esto podría ser útil?

+13

Porque C es la encarnación perfecta del tipado débil. "Los tipos son solo por diversión, si no te gusta el tipo de cosas, solo escríbelo";) – delnan

+1

Ley de Osborn http://www.anvari.org/fortune/Fortune_Cookies/95_osborn-s-law-variables-won- t.html – Jaydee

+2

Porque C & C++ te permitirá hacer lo que quieras, incluso si quieres hacer algo realmente, realmente estúpido. –

Respuesta

30

Está permitido porque ha invalidado la constness de ptr1 fundiéndolo en un puntero no const. Esta es la razón por la cual los moldes pueden ser muy peligrosos.

Tenga en cuenta que algunos compiladores, como GCC, no le permitirán descartar el estado const de esta manera.

+0

+1 buena respuesta. –

+1

Tenga en cuenta que este código puede bloquearse si el valor de la const está almacenado en la memoria de solo lectura. –

+1

Mi gcc (4.4.3) acepta el código sin ningún tipo de queja. el compilador de codepad también lo acepta (http://codepad.org/V9Oi65Cf) como lo hace ideone (http://ideone.com/tjCq7) – pmg

12

Has roto la garantía de la constancia jugando trucos de puntero. No se garantiza que funcione todo el tiempo y podría invocar casi cualquier comportamiento según el sistema/sistema operativo/compilador al que lo aplique.

No hagas eso.

O al menos no hagas eso a menos que realmente sepas lo que estás haciendo e incluso entiendas que no es en absoluto portátil.

5

const realmente significa "readonly".

Como has descubierto, el valor de los objetos const puede cambiar, pero tienes que usar métodos tortuosos para hacerlo. Y al usar estos métodos desviados, invocará Comportamiento no definido.

+1

@Konrad: tanto en C como en C++ es un comportamiento indefinido modificar un objeto que contenga una calificación 'const' en su definición. Estos objetos podrían estar ubicados en la memoria de solo lectura. –

+0

@Bart: tienes razón, por supuesto ... de alguna manera había ignorado que esta era una definición variable. –

2

Funciona porque ha emitido explícitamente el const ness de la punta de distancia. Mientras ptr1 es un puntero a un const int, ptr2 es un puntero a un int, por lo que su punta es variable.

Existen muy pocas buenas razones para hacer esto, pero puede encontrar un caso donde se evite la duplicación de código. Por ejemplo:

const char* letter_at(char* input, int position) 
{ 
    ... stuff ... 
    return &found_char; 
} 

char* editable_letter_at(char* input, int position) 
{ 
    return (char*)(letter_at(input, position)); 
} 

(ejemplo algo mutilada del ejemplo de C++ en el artículo 3 de efectivo de C++ 3ª)

2

Si va a desechar constness en un programa en C++, por favor utilice un estilo más C++ de fundición:

int *ptr2 = const_cast<int*>(ptr1); 

Si llegas a tener problemas relacionados con este tipo de fundición (usted, usted siempre lo hacen) entonces se puede encontrar en el que ocurrió mucho más rápidamente mediante la búsqueda de "const_cast" en lugar de intentar todas las combinaciones bajo el sol. Además, ayudará a otros nuestros que pueden o no venir después de ti.

Hay solo algunas situaciones en las que puedo ver que esto es útil. La mayoría de ellos son casos de esquina. Evitaría esto a toda costa si estás desarrollando en C++.

10

"Permitido" es lo opuesto a "prevenido", pero también es lo opuesto a "prohibido". Has visto que no se previene la modificación de tu objeto const, pero eso no significa exactamente que esté permitido.

La modificación de un objeto const no está "permitida" en el sentido de ser "permitida". El comportamiento de su programa no está definido por el estándar (consulte 6.7.3/5). Sucede que en su implementación, en esa ejecución, vio el valor 3. En otra implementación o en otro día, es posible que vea un resultado diferente.

Sin embargo, no está "prevenido", porque con la forma en que C funciona, detectarlo en tiempo de compilación es un problema de detención. Detectarlo en tiempo de ejecución requiere controles adicionales en todos los accesos a la memoria. El estándar está diseñado para no imponer una gran sobrecarga en las implementaciones.

La razón por la que se rechaza const es compatible, es porque si tiene un puntero const para un objeto no const, el lenguaje le permite (en ambos sentidos) modificar ese objeto. Para hacerlo, debes deshacerte del calificador const. La consecuencia de esto es que los programadores también pueden descartar calificadores const desde punteros a objetos que en realidad son const.

Aquí hay un ejemplo (un poco tonto) de código que descarta un calificador const por esa razón:

typedef struct { 
    const char *stringdata; 
    int refcount; 
} atom; 

// returns const, because clients aren't allowed to directly modify atoms, 
// just read them 
const atom *getAtom(const char *s) { 
    atom *a = lookup_in_global_collection_of_atoms(s); 
    if (a == 0) { 
     // error-handling omitted 
     atom *a = malloc(sizeof(atom)); 
     a->stringdata = strdup(s); 
     a->refcount = 1; 
     insert_in_global_collection_of_atoms(a); 
    } else { 
     a->refcount++; 
    } 
    return a; 
} 

// takes const, because that's what the client has 
void derefAtom(const atom *a) { 
    atom *tmp = (atom*)a; 
    --(tmp->refcount); 
    if (tmp->refcount == 0) { 
     remove_from_global_collection_of_atoms(a); 
     free(atom->stringdata); 
     free(atom); 
    } 
} 
void refAtom(const atom *a) { 
    ++(((atom*) a)->refcount); 
} 

Es una tontería, porque un diseño mejor sería remitir-declarar atom, para hacer punteros a ella completamente opaco y proporciona una función para acceder a los datos de cadena. Pero C no requiere que encapsule todo, le permite devolver punteros a tipos completamente definidos, y quiere admitir este tipo de uso constante para presentar una vista de solo lectura de un objeto que es "realmente" modificable.

2

C moldes le dicen al compilador que usted sabe lo que está haciendo, y que se asegurará de que todo funcione al final. Si los usa sin entender exactamente lo que está haciendo, puede meterse en problemas.

En este caso, el compilador tiene perfectamente el derecho de poner i en la memoria de solo lectura, de modo que este código se bloqueará cuando se ejecute. Alternativamente, podría funcionar como lo vio. El estándar especifica esto como un comportamiento indefinido, por lo que literalmente puede pasar cualquier cosa.

C fue diseñado originalmente para escribir Unix, y deliberadamente le da al programador una gran libertad en la manipulación de datos, ya que en la escritura del sistema operativo suele ser muy útil escribir código altamente específico que haga cosas que no sean seguras. cualquier otro contexto. En el código de aplicación regular, el casting debe hacerse con precaución.

Y no use moldes de estilo C en C++. C++ tiene su propia familia de moldes que son fáciles de buscar en el código y que a menudo especifican más qué hace realmente el elenco. En este caso particular, usaría const_cast, que muestra exactamente lo que está haciendo y es fácil de encontrar.

2

Porque C sabe que los programadores siempre saben lo que hacen y siempre hacen lo correcto. Usted puede incluso lanzarlo como:

void* ptr2 = (int(*)(void(*)(char*), int[])) ptr1; 
(*(int*)ptr2) = 3; 

y ni siquiera se quejará en absoluto.

1

La declaración const int i = 2; significa que el símbolo/i variables tiene un valor 2 y el valor no se puede cambiar usando i; pero no hay garantía de que el valor no se pueda cambiar por algún otro medio (como en el ejemplo de OP).