Llamada de función con puntero a no constantes y puntero a argumentos constantes de la misma dirección

14

Quiero escribir una función que ingrese una matriz de datos y genere otra matriz de datos utilizando punteros.

Me pregunto cuál es el resultado si ambos srcy dsten punta a la misma dirección, porque sé compilador puede optimizar para la const. ¿Es un comportamiento indefinido? (Etiqueté C y C ++ porque no estoy seguro de si la respuesta puede diferir entre ellos, y quiero saber sobre ambos).

void f(const char *src, char *dst) {
    dst[2] = src[0];
    dst[1] = src[1];
    dst[0] = src[2];
}

int main() {
    char s[] = "123";
    f(s,s);
    printf("%s\n", s);
    return 0;
}

Además de la pregunta anterior, ¿está bien definido si elimino el constcódigo original?

Colita
fuente

Respuestas:

17

Si bien es cierto que el comportamiento está bien definido - es no cierto que los compiladores pueden "optimizar para la const" en el sentido de que usted se refiere.

Es decir, un compilador no puede asumir que solo porque un parámetro sea a const T* ptr, la memoria a la que apunta ptrno se cambiará a través de otro puntero. Los punteros ni siquiera tienen que ser iguales. El constes una obligación, no una garantía, una obligación suya (= la función) de no realizar cambios a través de ese puntero.

Para tener esa garantía, debe marcar el puntero con la restrictpalabra clave. Por lo tanto, si compila estas dos funciones:

int foo(const int* x, int* y) {
    int result = *x;
    (*y)++;
    return result + *x;
}

int bar(const int* x, int* restrict y) {
    int result = *x;
    (*y)++;
    return result + *x;
}

la foo()función debe leer dos veces x, mientras que bar()solo necesita leerla una vez:

foo:
        mov     eax, DWORD PTR [rdi]
        add     DWORD PTR [rsi], 1
        add     eax, DWORD PTR [rdi]  # second read
        ret
bar:
        mov     eax, DWORD PTR [rdi]
        add     DWORD PTR [rsi], 1
        add     eax, eax              # no second read
        ret

Mira esto en vivo GodBolt.

restrictes solo una palabra clave en C (desde C99); desafortunadamente, no se ha introducido en C ++ hasta ahora (por la mala razón de que es más complicado introducirlo en C ++). Sin embargo, muchos compiladores sí lo admiten __restrict.

En pocas palabras: el compilador debe admitir su caso de uso "esotérico" al compilar f(), y no tendrá ningún problema.


Vea esta publicación sobre casos de uso para restrict.

einpoklum
fuente
constno es "una obligación suya (= la función) no hacer cambios a través de ese puntero". El estándar C permite que la función se elimine constmediante una conversión y luego se modifique el objeto a través del resultado. Esencialmente, constes solo una asesoría y una conveniencia para el programador para ayudar a evitar modificar un objeto sin darse cuenta.
Eric Postpischil
@EricPostpischil: Es una obligación de la que puedes salirte.
Einpoklum
Una obligación de la que puede salirse no es una obligación.
Eric Postpischil
2
@EricPostpischil: 1. Estás dividiendo pelos aquí. 2. Eso no es verdad.
Einpoklum
1
Es por eso memcpyy strcpyse declaran con restrictargumentos, mientras memmoveque no lo es: solo este último permite la superposición entre los bloques de memoria.
Barmar
5

Esto está bien definido (en C ++, ya no estoy seguro en C), con y sin el constcalificador.

Lo primero que debe buscar es la estricta regla de alias 1 . Si srcy dstapunta al mismo objeto:

Con respecto al constcalificador, podría argumentar que, dado que cuando dst == srcsu función modifica efectivamente a qué srcpuntos, srcno debería calificarse como const. Así no es como constfunciona. Deben considerarse dos casos:

  1. Cuando un objeto se define como const, como en char const data[42];, modificarlo (directa o indirectamente) conduce a Comportamiento indefinido.
  2. Cuando constse define una referencia o puntero a un objeto, como en char const* pdata = data;, se puede modificar el objeto subyacente siempre que no se haya definido como const2 (ver 1.). Entonces lo siguiente está bien definido:
int main()
{
    int result = 42;
    int const* presult = &result;
    *const_cast<int*>(presult) = 0;
    return *presult; // 0
}

1) ¿Cuál es la estricta regla de alias?
2) ¿Es const_castseguro?

YSC
fuente
¿Quizás el OP significa un posible reordenamiento de las tareas?
Igor R.
char*y char const*no son compatibles _Generic((char *) 0, const char *: 1, default: 0))evalúa a cero.
Eric Postpischil
La frase "Cuando constse define una referencia o un puntero a un objeto" es incorrecta. Quiere decir que cuando se define una referencia o un puntero a un tipoconst calificado , eso no significa que el objeto al que está configurado para apuntar no puede modificarse (por varios medios). (Si el puntero apunta a un objeto, eso significa que el objeto es por definición, por lo que el comportamiento de tratar de modificarlo no está definido.)constconst
Eric Postpischil
@Eric, solo soy tan específico cuando la pregunta es acerca de Estándar o etiquetada language-lawyer. La exactitud es un valor que aprecio, pero también soy consciente de que viene con más complejidad. Aquí, decidí ir por simplicidad y oraciones fáciles de entender, porque creo que esto es lo que quería OP OP. Si piensa lo contrario, responda, estaré entre los primeros en votarlo. De todos modos, gracias por tu comentario.
YSC
3

Esto está bien definido en C. Las reglas de alias estrictas no se aplican con el chartipo, ni con dos punteros del mismo tipo.

No estoy seguro de lo que quieres decir con "optimizar para const". Mi compilador (GCC 8.3.0 x86-64) genera exactamente el mismo código para ambos casos. Si agrega el restrictespecificador a los punteros, el código generado es ligeramente mejor, pero eso no funcionará para su caso, ya que los punteros son los mismos.

(C11 §6.5 7)

Un objeto tendrá acceso a su valor almacenado solo mediante una expresión lvalue que tenga uno de los siguientes tipos:
- un tipo compatible con el tipo efectivo del objeto,
- una versión calificada de un tipo compatible con el tipo efectivo del objeto,
- un tipo que es el tipo con signo o sin signo correspondiente al tipo efectivo del objeto,
- un tipo que es el tipo con signo o sin signo correspondiente a una versión calificada del tipo efectivo del objeto,
- un tipo agregado o de unión que incluye uno de los tipos antes mencionados entre sus miembros (incluido, recursivamente, un miembro de una unión agregada o contenida), o
- un tipo de carácter.

En este caso (sin restrict), siempre obtendrá 121como resultado.

SS Anne
fuente