¿Por qué usar una variable int para un pin cuando const int, enum o #define tiene mucho más sentido?

24

¿Por qué las personas usan una variable para especificar un número de pin cuando es poco probable que el pin cambie durante la ejecución del código?

Muchas veces veo un intser utilizado para una definición de pin,

int led = 13;

cuando el uso de un const int

const int led = 13;

o enum, o#define

#define LED 13

Tiene mucho más sentido.

Incluso está en los tutoriales en el sitio de Arduino, por ejemplo, el primer tutorial que ejecuta la mayoría de la gente, Blink .

Me leído en alguna parte que const intse prefiere sobre #define. ¿Por qué no se fomenta esto desde el principio, en lugar de permitir que las personas desarrollen malos hábitos desde el principio? Lo noté hace un tiempo, pero recientemente ha comenzado a irritarme, de ahí la pregunta.

Memoria / procesamiento / computación inteligente es una const int, enumo para el caso #define, mejor que una llanura int, es decir, ocupa menos memoria, almacenado en memoria diferentes (Flash, EEPROM, SRAM), más rápida ejecución, más rápido para compilar?


Esto puede parecer un duplicado de ¿Es mejor usar #define o const int para constantes? , pero estoy abordando la pregunta de por qué las personas usan variables y cómo mejora el rendimiento cuando no lo hacen, en lugar de qué tipo de constante es mejor.

Greenonline
fuente
99
Porque terrible engendra terrible. La mayoría de los aficionados no son programadores experimentados y, por lo tanto, enseñan malos hábitos a otros aficionados.
Ignacio Vázquez-Abrams
1
Con los pines en particular, la forma simplista de las funciones básicas de la API de arduino como digitalWrite no fomenta el diseño embebido adecuado, es decir, el uso de máscaras y una sola dirección de memoria para todo el puerto
crasic

Respuestas:

20
const int led = 13;

Ese es el método correcto. O incluso:

const byte led = 13;

¿Cuántos pines tienes?

Algunos de los tutoriales no pasaron por tanto control de calidad como podrían haberlo hecho.

El rendimiento será mejor usando const byte, en comparación, intsin embargo, el compilador puede ser lo suficientemente inteligente como para darse cuenta de lo que está haciendo.

Lo que puede hacer es alentar suavemente a las personas a usar técnicas más eficientes al usarlas en su propio código.


Respuestas a comentarios

  1. Un comentarista ha sugerido que byteno es el estándar C. Esto es correcto, sin embargo, este es un sitio de Arduino StackExchange, y creo que es aceptable usar los tipos estándar proporcionados por el IDE de Arduino.

    En Arduino.h hay esta línea:

    typedef uint8_t byte;

    Tenga en cuenta que esto no es exactamente lo mismo que unsigned char. Ver uint8_t vs unsigned char y ¿ Cuándo es uint8_t ≠ unsigned char? .

  2. Otro comentarista ha sugerido que el uso de byte no necesariamente mejorará el rendimiento, porque los números menores a los que intse promocionarán int(consulte Reglas de promoción de enteros si desea obtener más información al respecto).

    Sin embargo, en el contexto de un identificador constante , el compilador generará un código eficiente en cualquier caso. Por ejemplo, desarmar "parpadeo" le da esto en la forma original:

    00000086 <loop>:
      86:   8d e0           ldi r24, 0x0D   ; 13
      88:   61 e0           ldi r22, 0x01   ; 1
      8a:   1b d1           rcall   .+566       ; 0x2c2 <digitalWrite>

    De hecho, genera el mismo código si 13:

    • Es un literal
    • Es un #define
    • Es un const int
    • Es un const byte

El compilador sabe cuándo puede caber un número en un registro y cuándo no. Sin embargo, es una buena práctica usar una codificación que indique su intención . Dejarlo en constclaro deja claro que el número no cambiará, y dejarlo claro byte(o uint8_t) deja claro que espera un número pequeño.


Mensajes de error confusos

Otra razón importante para evitar #definees los mensajes de error que recibe si comete un error. Considere este bosquejo de "parpadeo" que tiene un error:

#define LED = 13;

void setup() {
  pinMode(LED, OUTPUT);      // <---- line with error
}

void loop() {
  digitalWrite(LED, HIGH);   // <---- line with error 
  delay(1000);             
  digitalWrite(LED, LOW);    // <---- line with error
  delay(1000);              
}

En la superficie se ve bien, pero genera estos mensajes de error:

Blink.ino: In function ‘void setup()’:
Blink:4: error: expected primary-expression before ‘=’ token
Blink:4: error: expected primary-expression before ‘,’ token
Blink:4: error: expected `;' before ‘)’ token
Blink.ino: In function ‘void loop()’:
Blink:8: error: expected primary-expression before ‘=’ token
Blink:8: error: expected primary-expression before ‘,’ token
Blink:8: error: expected `;' before ‘)’ token
Blink:10: error: expected primary-expression before ‘=’ token
Blink:10: error: expected primary-expression before ‘,’ token
Blink:10: error: expected `;' before ‘)’ token

Miras la primera línea resaltada (línea 4) y ni siquiera ves un símbolo "=". Además, la línea se ve bien. Ahora es bastante obvio cuál es el problema aquí ( = 13se está sustituyendo LED), pero cuando la línea está 400 líneas más abajo en el código, no es obvio que el problema esté en la forma en que se define el LED.

He visto a personas caer en esto muchas veces (incluido yo mismo).

Nick Gammon
fuente
¿Cuántos pines tienes? es un muy buen punto Nick, ya que la mayoría de los tableros solo tienen el rango de las decenas, no cientos (es decir, más de 255), por lo que intes excesivo ... es decir, hasta que Arduino finalmente salga con el tablero Tera ... :-)
Greenonline
2
C no tiene un bytetipo . ¿Quiere decir unsigned char.
Kevin
El rendimiento no será necesariamente mejor en bytelugar de int, ya que, en la mayoría de los contextos, el valor entero con tipos más pequeños de los que intse promueven int.
Pete Becker
1
C doesn't have a byte type. You mean unsigned char.- Mi respuesta fue en el contexto Arduino, que tiene esto typedef uint8_t byte;. Entonces, para un Arduino, usar byteestá bien.
Nick Gammon
Performance won't necessarily be better with byte instead of int- Ver publicación modificada.
Nick Gammon
19

Como Ignacio dice correctamente, es básicamente porque no saben mejor. Y no saben mejor porque las personas que les enseñaron (o los recursos que usaron cuando aprendieron) no sabían mejor.

Gran parte del código y los tutoriales de Arduino están escritos por personas que nunca han recibido capacitación en programación y son muy "autodidactas" de los recursos de personas que son muy autodidactas sin la capacitación adecuada en programación.

Muchos de los fragmentos de código tutorial que veo alrededor del lugar (y especialmente aquellos que solo están disponibles en los videos de YouTube --- urgh) serían una marca de falla si los marcara en un examen.

Sí, constse prefiere a sobre una no constante, e incluso sobre una #define, porque:

  • A const(como a #define, a diferencia de un no constante) no asigna ninguna RAM
  • A const(como un no constante, pero a diferencia de a #define) le da al valor un tipo explícito

El segundo punto allí es de particular interés. A menos que se indique específicamente lo contrario con la conversión de tipos incrustada ( (long)3) o un sufijo de tipo ( 3L) o la presencia de un punto decimal ( 3.0), #defineun número siempre será un número entero y todas las matemáticas realizadas en ese valor serán como si fuera un entero. La mayoría de las veces eso no es un problema, pero puede encontrarse con escenarios interesantes cuando intenta #defineun valor que es más grande de lo que un entero puede almacenar, como #define COUNT 70000y luego realizar una operación matemática con otros intvalores. Al usar un const, puede decirle al compilador "Este valor debe tratarse como este tipo de variable", por lo que en su lugar usaría: const long count = 70000;y todo funcionaría como se esperaba.

También tiene el efecto knock-on que comprueba el tipo al pasar el valor alrededor del lugar. Intente pasar un const longa una función que espera un inty se quejaría de reducir el rango de variables (o incluso no compilará por completo según el escenario). Haga eso con un #definey simplemente continuaría silenciosamente dándole los resultados incorrectos y lo dejaría rascándose la cabeza durante horas.

Majenko
fuente
77
Vale la pena señalar que una constvariable puede requerir RAM, dependiendo del contexto, por ejemplo, si se inicializa utilizando el valor de retorno de una función no constexpr.
Peter Bloomfield
Del mismo modo, const int foo = 13; bar(&foo);definitivamente requerirá que el compilador asigne memoria real para foo.
Ilmari Karonen
3
Si define una macro que se expande a un valor que no cabe en un intcompilador, el compilador considera que el valor tiene el tipo más pequeño en el que se ajustará (reglas de módulo sobre con signo y sin signo). Si está en un sistema de int16 bits, #define count 70000tendrá el countaspecto de un long, como si se hubiera definido como const long count = 70000;. Además, si pasa cualquiera de esas versiones counta una función que espera int, cualquier compilador sensato las tratará de la misma manera.
Pete Becker
1
Estoy de acuerdo con @PeteBecker: una construcción como #define COUNT 70000no se trunca en un int, pero el compilador lo trata como un tipo lo suficientemente grande como para contener ese número. Es cierto que puede no ser obvio cuando usa COUNTque no es un int, pero podría decir lo mismo sobre un de const longtodos modos.
Nick Gammon
2
"un #define siempre será un número entero" Eso no es cierto. Está tomando las reglas de los literales enteros y aplicándolos a las macros de preprocesador. Es como comparar manzanas y música pop. La expresión COUNTen su ejemplo se reemplaza antes de la compilación con la expresión 70000, que tiene un tipo definido por las reglas de literales, al igual que 2o 13Lo 4.0son definidos por las normas de literales. El hecho de que utilice #definepara alias esas expresiones es irrelevante. Puede usar #definealias de fragmentos arbitrarios de código C, si lo desea.
ligereza corre con Mónica el
2

Como novato de 2 semanas en Arduino, retomaría la idea general de que Arduino está ocupado por no programadores. La mayoría de los bocetos que he examinado, incluidos los del sitio de Arduino, muestran una falta total de orden, con bocetos que no funcionan y apenas un comentario coherente a la vista. Los diagramas de flujo no existen, y las "Bibliotecas" son un revoltijo no moderado.

Jim
fuente
0

Mi respuesta es ... lo hacen porque funciona. Me cuesta mucho no hacer una pregunta en mi respuesta como "¿por qué tiene que estar 'equivocado'?"

linhartr22
fuente
3
Una característica distintiva de un buen programador es que el código siempre refleja sus intenciones.
Ignacio Vazquez-Abrams
1
Todavía estamos hablando de Arduinos, ¿verdad? ;)
linhartr22
3
Arduino ya tiene una mala reputación en la comunidad más grande de EE debido a los diseños de hardware mediocres a terribles presentados por la comunidad. ¿No deberíamos tratar de dar una mierda por algo ?
Ignacio Vazquez-Abrams
2
"La mayoría de los proyectos no van a involucrar riesgos de vida o finanzas ..." No hay sorpresa allí. ¿Quién querría involucrar a Arduino donde hay alguna posibilidad de riesgo después de mirar a la comunidad en general?
Ignacio Vazquez-Abrams
2
Está "mal" no porque no funcione en una situación particular sino porque, en comparación con hacerlo "bien", hay más situaciones en las que no funciona. Esto hace que el código sea frágil; Los cambios en el código pueden causar fallas misteriosas que consumen el tiempo de depuración. La verificación de tipo y los mensajes de error del compilador están ahí para ayudarlo a detectar ese tipo de errores más temprano que tarde.
Curt J. Sampson