language c language-lawyer c99 c11

c - language - iso 9899



¿Es int main(){}(sin "void") válido y portátil en ISO C? (3)

No.

De acuerdo con la redacción normativa de la norma, una definición que use paréntesis vacíos sin la palabra clave void no es una de las formas que deben aceptarse, y estrictamente hablando, el comportamiento de tal programa no está definido.

Referencia: N1570 sección 5.1.2.2.1. (La norma ISO C de 2011 publicada, que no está disponible de forma gratuita, tiene la misma redacción que el borrador N1570).

El párrafo 1 dice:

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 de int y sin parámetros:

int main(void) { /* ... */ }

o con dos parámetros (referidos aquí como argc y argv , 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; o de alguna otra manera definida por la implementación.

El uso de la palabra "deberá" fuera de una restricción significa que cualquier programa que la viole tenga un comportamiento indefinido. Así que si, por ejemplo, escribo:

double main(unsigned long ocelots) { return ocelots / 3.14159; }

no se requiere que un compilador conforme imprima un diagnóstico, pero tampoco es necesario compilar el programa o, si lo hace, compilarlo de alguna manera en particular.

Si int main() fuera equivalente a int main(void) , sería válido y portátil para cualquier implementación alojada conforme. Pero no es equivalente.

int main(void) { }

proporciona tanto una declaración (en este caso, un prototipo) como una definición . La declaración, mediante el uso de la palabra clave void , especifica que la función no tiene parámetros. La definición especifica lo mismo.

Si por el contrario escribo:

int main() { }

entonces estoy usando una declaración y definición de estilo antiguo . (Tales declaraciones y definiciones son obsoletas , pero aún son parte de la definición del lenguaje, y todos los compiladores que las conforman deben apoyarlas).

Como una declaración, no especifica el número o el tipo (s) de argumentos esperados por la función. Como definición, no define parámetros, pero los compiladores no necesitan usar esa información para diagnosticar llamadas incorrectas.

El DR # 317 incluye la decisión de 2006 del comité de la norma C de que una definición con () no proporciona un prototipo equivalente a uno con (void) (gracias a hvd por encontrar esa referencia).

C permite que main sea ​​llamado recursivamente. Supongamos que escribo:

int main(void) { if (0) { main(42); } }

El prototipo visible int main(void) especifica que main no toma argumentos. Una llamada que intenta pasar uno o más argumentos viola una restricción, que requiere un diagnóstico en tiempo de compilación.

O supongamos que escribo:

int main() { if (0) { main(42); } }

Si se ejecutara la llamada main(42) , tendría un comportamiento indefinido, pero no viola una restricción y no se requiere ningún diagnóstico. Ya que está protegido por if (0) , la llamada nunca ocurre y el comportamiento indefinido nunca ocurre realmente. Si asumimos que int main() es válido, este compilador debe aceptar este programa. Pero debido a eso, demuestra que int main() no es equivalente a int main(void) , y por lo tanto no está cubierto por 5.1.2.2.1.

Conclusión: Siguiendo la redacción de la norma, se permite que una implementación documente que se permite int main() { } . Si no lo documenta, todavía está permitido aceptarlo sin quejarse. Pero un compilador conforme también puede rechazar int main() { } , porque no es una de las formas permitidas por el estándar y, por lo tanto, su comportamiento no está definido.

Pero todavía hay una pregunta abierta: ¿Fue esa la intención de los autores de la norma?

Antes de la publicación del estándar ANSI C de 1989, la palabra clave void no existía. Los programas Pre-ANSI (K&R) C definirían main ya sea como

main()

o como

int main()

Uno de los objetivos principales de la norma ANSI era agregar nuevas funciones (incluidos prototipos) sin romper el código pre-ANSI existente. Declarar que int main() ya no es válido habría violado ese objetivo.

Mi sospecha es que los autores del estándar C no intentaron hacer que int main() inválido. Pero el estándar tal como está escrito no refleja esa intención; al menos permite que un compilador de C compatible rechace int main() .

Hablando en términos prácticos , es casi seguro que puedes salirte con la tuya. Cada compilador de C que he probado alguna vez aceptará

int main() { return 0; }

Sin queja, con comportamiento equivalente a

int main(void) { return 0; }

Pero por una variedad de razones:

  • Siguiendo tanto la letra como la intención de la norma;
  • Evitar el uso de una función obsoleta (una norma futura podría eliminar las definiciones de funciones antiguas);
  • Mantener buenos hábitos de codificación (la diferencia entre () y (void) es importante para funciones distintas de main que son llamadas por otras funciones

Recomiendo siempre escribir int main(void) lugar de int main() . Indica la intención más claramente, y puede estar 100% seguro de que su compilador lo aceptará, en lugar del 99.9%.

El estándar C especifica dos formas de definición para main para una implementación alojada:

int main(void) { /* ... */ }

y

int main(int argc, char *argv[]) { /* ... */ }

Puede definirse de maneras que sean "equivalentes" a las anteriores (por ejemplo, puede cambiar los nombres de los parámetros, reemplazar int por un nombre typedef definido como int , o escribir char *argv[] como char **argv ).

También se puede definir "de alguna otra manera definida por la implementación", lo que significa que cosas como int main(int argc, char *argv[], char *envp) son válidas si la implementación las documenta.

La cláusula "de alguna otra manera definida por la implementación" no estaba en la norma 1989/1990; fue agregado por el estándar de 1999 (pero el estándar anterior permitía extensiones, por lo que una implementación aún podría permitir otras formas).

Mi pregunta es la siguiente: dado el estándar ISO C actual (2011), es una definición del formulario

int main() { /* ... */ }

¿Es válido y portátil para todas las implementaciones alojadas?

(Tenga en cuenta que no estoy tratando el void main ni el uso de int main() sin paréntesis en C ++. Esto es solo sobre la distinción entre int main(void) e int main() en ISO C).


Sí.

int main() { /* ... */ }

es equivalente a

int main(void) { /* ... */ }

N1570 5.1.2.2.1 / 1

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 de int y sin parámetros :

int main(void) { /* ... */ }

o con dos parámetros (referidos aquí como argc y argv, 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; o de alguna otra manera definida por la implementación.

6.7.6.3/14

Una lista de identificadores declara solo los identificadores de los parámetros de la función. Una lista vacía en un declarador de función que forma parte de una definición de esa función especifica que la función no tiene parámetros. La lista vacía en un declarador de función que no forma parte de una definición de esa función especifica que no se proporciona información sobre el número o los tipos de parámetros.

(énfasis mío)

Como lo establece claramente la norma, la definición int main() { /* ... */ } especifica que la función main no tiene parámetros. Y está claro para todos nosotros, que esta definición de función especifica que el tipo de retorno de la función main es int . Y, dado que 5.1.2.2.1 no requiere que la declaración de main tenga un prototipo, podemos afirmar con seguridad que la definición int main() { /* ... */ } satisface todos los requisitos impuestos por la norma ( It [the main funtion] shall be defined with a return type of int and with no parameters, or [some other forms] . ).

No obstante, nunca debe usar int main() {} en su código, ya que "el uso de declaradores de función con paréntesis vacíos (no declaradores de tipo de parámetro de formato de prototipo) es una característica obsoleta". (6.11.6), y debido a que esta forma de definición no incluye un declarador de prototipo de función, el compilador no verificará si el número y los tipos de argumentos son correctos.

N1570 6.5.2.2/8

No se realizan otras conversiones implícitamente; en particular, el número y los tipos de argumentos no se comparan con los de los parámetros en una definición de función que no incluye un declarador de prototipo de función .

(énfasis mío)


Una clara indicación de que int main() está destinado a ser válido, independientemente de si la norma proporciona la redacción precisa para hacerlo válido, es el hecho de que int main() se usa ocasionalmente en la norma sin que nadie plantee ninguna objeción. Si bien los ejemplos no son normativos, sí indican la intención.

6.5.3.4 Los operadores sizeof y _Alignof

8 EJEMPLO 3 En este ejemplo, el tamaño de una matriz de longitud variable se calcula y se devuelve desde una función:

#include <stddef.h> size_t fsize3(int n) { char b[n+3]; // variable length array return sizeof b; // execution time sizeof } int main() { size_t size; size = fsize3(10); // fsize3 returns 13 return 0; }

6.7.6.3 Declaradores de funciones (incluyendo prototipos)

20 EJEMPLO 4 El siguiente prototipo tiene un parámetro modificado de manera variable.

void addscalar(int n, int m, double a[n][n*m+300], double x); int main() { double b[4][308]; addscalar(4, 2, b, 2.17); return 0; } void addscalar(int n, int m, double a[n][n*m+300], double x) { for (int i = 0; i < n; i++) for (int j = 0, k = n*m+300; j < k; j++) // a is a pointer to a VLA with n*m+300 elements a[i][j] += x; }

En cuanto al texto normativo real de la norma, creo que se está leyendo demasiado en "equivalente". Debe quedar bastante claro que

int main (int argc, char *argv[]) { (void) argc; (void) argv; return 0; }

es valido, y eso

int main (int x, char *y[]) { (void) argc; (void) argv; return 0; }

no es válido. No obstante, el estándar establece explícitamente en el texto normativo que se puede usar cualquier nombre, lo que significa que int main (int argc, char *argv[]) e int main (int x, char *y[]) cuentan como equivalentes para los fines de 5.1.2.2.1. El significado estricto en inglés de la palabra "equivalente" no es cómo debe leerse.

Una interpretación algo más flexible de la palabra es lo que sugiere Keith Thompson en su respuesta.

Una interpretación igualmente válida, incluso más flexible, de la palabra sí permite int main() : tanto int main(void) como int main() definen main como una función que devuelve int y no toma parámetros.

Ni el DR estándar ni ningún DR oficial responde actualmente a la pregunta de a qué interpretación se dirige, por lo que la pregunta no tiene respuesta, pero los ejemplos sugieren fuertemente esa última interpretación.