efecto - aliasing video
¿ISO C permite el aliasing de los punteros argv[] suministrados a main()? (3)
Según mi lectura, la respuesta al titular es "sí", ya que en ninguna parte está explícitamente prohibida y en ninguna parte el estándar exige o requiere el uso de argv restringido-calificado, pero la respuesta podría activar la interpretación de "y conservar su último -Los valores almacenados entre el inicio del programa y la terminación del programa ".
Estoy de acuerdo en que la norma no prohíbe explícitamente que los elementos del vector de argumento sean alias entre sí. No creo que las disposiciones de modificabilidad y retención de valor contradigan esa posición, pero me sugieren que el comité no consideró la posibilidad de crear alias.
La importancia práctica de esta pregunta es que si la respuesta es de hecho "sí", un programa portátil que desee modificar las cadenas en argv debe realizar primero (el equivalente a) POSIX strdup () en ellas por seguridad.
De hecho, esa es exactamente la razón por la que creo que el comité ni siquiera consideró la posibilidad. Si lo hubieran hecho, seguramente habrían incluido al menos una nota a pie de página con ese mismo efecto, o si no hubieran especificado explícitamente que todas las cadenas de argumentos son distintas.
Me inclino a pensar que este detalle escapó a la atención del comité porque, en la práctica, las implementaciones sí brindan cadenas distintas y, además, es raro que los programas modifiquen sus cadenas de argumentos (aunque modificar el argumento es en sí mismo algo más común). Si el comité acordara emitir una interpretación oficial en esta área, entonces no me sorprendería que se enfrentaran a la posibilidad de un alias.
Hasta y a menos que se emita tal interpretación, sin embargo, tiene razón en que la conformidad estricta no le permite confiar a priori en los elementos de argv
que no tienen alias.
ISO C requiere que las implementaciones alojadas llamen a una función llamada main
. Si el programa recibe argumentos, se reciben como una matriz de punteros char*
, el segundo argumento en la definición de int main(int argc, char* argv[])
.
ISO C también requiere que las cadenas apuntadas por la matriz argv
sean modificables.
Pero, ¿pueden los elementos de argv
alias entre sí? En otras palabras, ¿puede existir i
, j
tal que
-
0 >= i && i < argc
-
0 >= j && j < argc
-
i != j
-
0 < strlen(argv[i])
-
strlen(argv[i]) <= strlen(argv[j])
-
argv[i]
aliasargv[j]
en el inicio del programa? Si es así, una escritura a través de argv[i][0]
también se vería a través de la cadena de aliasing argv[j]
.
Las cláusulas relevantes de la norma ISO C se encuentran a continuación, pero no me permiten responder de manera concluyente a la pregunta titular.
§ 5.1.2.2.1 Inicio del programa
La función llamada al inicio del programa se llama
main
. La implementación no declara ningún prototipo para esta función. Se definirá con un tipo de retorno deint
y sin parámetros:
int main(void) { /* ... */ }
o con dos parámetros (referidos aquí como
argc
yargv
, aunque se puede usar cualquier nombre, ya que son locales a la función en la que están declarados):
int main(int argc, char *argv[]) { /* ... */ }
o equivalente; 10) o de alguna otra manera definida por la implementación.
Si se declaran, los parámetros de la función
main
obedecerán las siguientes restricciones:
- El valor de
argc
será no negativo.argv[argc]
será un puntero nulo.- Si el valor de
argc
es mayor que cero, los miembros de la matrizargv[0]
través deargv[argc-1]
inclusive contendrán punteros a cadenas, a los que el entorno de host proporciona valores definidos por la implementación antes del inicio del programa. La intención es proporcionar a la información del programa determinada antes del inicio del programa desde otro lugar en el entorno alojado. Si el entorno del host no es capaz de suministrar cadenas con letras en mayúsculas y minúsculas, la implementación garantizará que las cadenas se reciban en minúsculas.- Si el valor de
argc
es mayor que cero, la cadena apuntada porargv[0]
representa el nombre del programa;argv[0][0]
será el carácter nulo si el nombre del programa no está disponible en el entorno host. Si el valor deargc
es mayor que uno, las cadenas apuntadas porargv[1]
través deargv[argc-1]
representan los parámetros del programa.- Los parámetros
argc
yargv
y las cadenas señaladas por la matrizargv
serán modificables por el programa y conservarán sus últimos valores almacenados entre el inicio del programa y la terminación del programa.
Según mi lectura, la respuesta a la pregunta del titular es "sí", ya que en ninguna parte está explícitamente prohibida y en ninguna parte el estándar exige o requiere el uso de char* restrict*
-calificado argv
, pero la respuesta podría centrarse en la interpretación de " y retener sus últimos valores almacenados entre el inicio del programa y la terminación del programa ". .
La importancia práctica de esta pregunta es que si la respuesta es de hecho "sí", un programa portátil que desee modificar las cadenas en argv
debe realizar primero (el equivalente a) POSIX strdup()
en ellas por seguridad.
Como punto de datos, he compilado y ejecutado los siguientes programas en varios sistemas. (Descargo de responsabilidad: estos programas tienen como objetivo proporcionar un punto de datos, pero como veremos, no terminan respondiendo la pregunta como se indica).
p1.c
:
#include <stdio.h>
#include <unistd.h>
int main()
{
char test[] = "test";
execl("./p2", "p2", test, test, NULL);
}
p2.c
:
#include <stdio.h>
int main(int argc, char **argv)
{
int i;
for(i = 1; i < argc; i++) printf("%s ", argv[i]); printf("/n");
argv[1][0] = ''b'';
for(i = 1; i < argc; i++) printf("%s ", argv[i]); printf("/n");
}
En cada lugar que lo he probado (bajo MacOS y varios tipos de Unix y Linux) se ha impreso.
test test
best test
Dado que la segunda línea nunca fue "la best best
", esto prueba que, en los sistemas probados, para cuando se ejecuta el segundo programa, las cadenas ya no tienen alias.
Por supuesto, esta prueba no prueba que las cadenas en argv
nunca puedan tener un alias, bajo ninguna circunstancia, bajo ningún sistema. Creo que todo lo que prueba es que, como era de esperar, cada uno de los sistemas operativos probados vuelve a copiar la lista de argumentos al menos una vez entre el momento en que p1
llama a execl
y el momento en que se invoca realmente p2
. En otras palabras, el vector de argumento construido por el programa invocador no se usa directamente en el programa llamado, y en el proceso de copiarlo, es (de nuevo no sorprendentemente) "normalizado", lo que significa que los efectos de cualquier aliasing se pierden.
(Digo que esto no es sorprendente porque si piensa en la forma en que realmente funciona la familia de llamadas del sistema de exec
, y la forma en que se presenta la memoria de proceso en sistemas similares a Unix, no hay forma de que la lista de argumentos del programa de invocación se pueda usar directamente ; tiene que ser copiado, al menos una vez, en el espacio de direcciones del nuevo proceso ejecutado. Además, cualquier método obvio y sencillo de copiar la lista de argumentos siempre y automáticamente va a "normalizarlo" de esta manera; El kernel tendría que hacer un trabajo significativo, adicional, totalmente innecesario para detectar y preservar cualquier alias.)
Por si acaso importa, modifiqué el primer programa de esta manera:
#include <stdio.h>
#include <unistd.h>
int main()
{
char test[] = "test";
char *argv[] = {"p2", test, test, NULL};
execv("./p2", argv);
}
Los resultados se mantuvieron sin cambios.
Con todo esto dicho, estoy de acuerdo en que este problema parece un descuido o error en los estándares. No tengo conocimiento de ninguna cláusula que garantice que las cadenas a las que apunta argv
sean distintas, lo que significa que un programa escrito en paranoia probablemente no puede depender de dicha garantía, sin importar qué tan probable sea (como lo demuestra esta respuesta) cualquier Es probable que la implementación razonable lo haga de esa manera.
La forma en que funciona en las plataformas comunes * nix (incluyendo Linux y Mac OS, probablemente también FreeBSD) es que argv
es una matriz de punteros en un área de memoria única que contiene las cadenas de argumentos una tras otra (separadas solo por el terminador nulo). El uso de execl()
no cambia esto, incluso si la persona que llama pasa el mismo puntero varias veces, la cadena de origen se copia varias veces, sin comportamiento especial para punteros idénticos (es decir, con alias) (un caso poco común sin gran beneficio para optimizar ).
Sin embargo, C no requiere esta implementación. El verdadero paranoico puede querer copiar cada cadena antes de modificarla, tal vez omitir las copias si la memoria está limitada y un bucle sobre argv
muestra que ninguno de los punteros es realmente alias (al menos entre los que el programa intenta modificar). Esto parece demasiado paranoico a menos que esté desarrollando software de vuelo o similar.