sum (sumndo 2 números) sin operador más

¿Alguien puede explicar la lógica de cómo agregar a y b ?

 #include  int main() { int a=30000, b=20, sum; char *p; p = (char *) a; sum = (int)&p[b]; //adding a and b! printf("%d",sum); return 0; } 

El + está oculto aquí:

 &p[b] 

esta expresión es equivalente a

 (p + b) 

Así que en realidad tenemos:

 (int) &p[b] == (int) ((char *) a)[b]) == (int) ((char *) a + b) == a + b 

Tenga en cuenta que esto invoca técnicamente un comportamiento indefinido como (char *) a tiene que apuntar a un objeto y una aritmética de puntero fuera de un objeto o una más allá del objeto invoca un comportamiento no definido.

El estándar C dice que E1[E2] es equivalente a *((E1) + (E2)) . Por lo tanto:

  &p[b] = &*((p) + (b)) = ((p) + (b)) = ((a) + (b)) = a + b 

p[b] es el elemento b-th de la matriz p . Es como escribir *(p + b) .

Ahora, al agregar & será como escribir: p + b * sizeof(char) que es p + b .
Ahora, tendrá (int)((char *) a + b) que es .. a + b .

Pero … cuando aún tengas + en tu teclado, úsalo.


Como @gerijeshchauhan aclaró en los comentarios, * y & son operaciones inversas, se cancelan entre sí. Entonces &*(p + b) es p + b .

  1. p se hace un puntero a char

  2. a se convierte en un puntero a char, lo que hace que p apunte a la memoria con la dirección a

  3. Luego, el operador de subíndices se usa para llegar a un objeto en un desplazamiento de b más allá de la dirección señalada por p. b es 20 y p + 20 = 30020. Luego, el operador address-of se usa en el objeto resultante para convertir la dirección de nuevo a int, y tiene el efecto de a + b

Los comentarios a continuación podrían ser más fáciles de seguir:

 #include  int main() { int a=30000, b=20, sum; char *p; //1. p is a pointer to char p = (char *) a; //2. a is converted to a pointer to char and p points to memory with address a (30000) sum = (int)&p[b]; //3. p[b] is the b-th (20-th) element from address of p. So the address of the result of that is equivalent to a+b printf("%d",sum); return 0; } 

Referencia: aquí

 char *p; 

p es un puntero (al elemento con tamaño 1 byte)


 p=(char *)a; 

ahora p apunta a la memoria con a dirección


 sum= (int)&p[b]; 

p puntero p se puede usar como matriz p[] (la dirección de inicio (en la memoria) de esta matriz es a )

p[b] significa obtener el elemento b-th – esta dirección del elemento es a+b

[(dirección de inicio) a + b (b-th elemento * tamaño del elemento (1 byte))]

&p[b] significa obtener la dirección del elemento en p[b] pero su dirección es a+b


Si usa el puntero a int (en su mayoría 4 bytes)

 int* p p = (int*)a; 

tu sum será a + (4 * b)

  int a=30000, b=20, sum; char *p; //1. p is a pointer to char p = (char *) a; 

a es de tipo int , y tiene el valor 30000 . La asignación anterior convierte el valor 30000 de int a char* y almacena el resultado en p .

La semántica de la conversión de enteros a punteros está (parcialmente) definida por el estándar C. Citando el borrador N1570 , sección 6.3.2.3, párrafo 5:

Un entero se puede convertir a cualquier tipo de puntero. Excepto como se especificó anteriormente, el resultado está definido por la implementación, podría no estar correctamente alineado, podría no apuntar a una entidad del tipo referenciado y podría ser una representación de captura.

con una nota a pie de página (no normativa):

Las funciones de mapeo para convertir un puntero a un entero o un entero a un puntero pretenden ser consistentes con la estructura de direccionamiento del entorno de ejecución.

El estándar no ofrece garantías sobre los tamaños relativos de los tipos int y char* ; cualquiera podría ser más grande que el otro, y la conversión podría perder información. El resultado de estas conversiones particulares es muy poco probable que sea un valor de puntero válido. Si se trata de una representación de captura, el comportamiento de la asignación es indefinido.

En un sistema típico que probablemente esté usando, char* es al menos tan grande como int , y las conversiones de entero a puntero probablemente reinterpreten los bits que forman la representación del entero como la representación de un valor de puntero.

  sum = (int)&p[b]; 

p[b] es por definición equivalente a *(p+b) , donde el + denota aritmética de punteros. Dado que el puntero apunta a char , y un char es, por definición, 1 byte, la adición avanza la dirección apuntada a por b bytes en la memoria (en este caso 20).

Pero p probablemente no sea un puntero válido, por lo que cualquier bash de realizar aritmética en él, o incluso para acceder a su valor, tiene un comportamiento indefinido.

En la práctica, la mayoría de los comstackdores de C generan código que no realiza comprobaciones adicionales. El énfasis está en la ejecución rápida del código correcto, no en la detección de código incorrecto. Por lo tanto, si la asignación anterior a p establece en una dirección correspondiente al número 30000 , entonces la adición de b , o 20, a esa dirección probablemente producirá una dirección correspondiente al número 30020 .

Esa dirección es el resultado de (p+b) ; ahora el operador [] aplica implícitamente el operador * a esa dirección, dándole el objeto al que apunta esa dirección – conceptualmente, este es un objeto char almacenado en una dirección correspondiente al número entero 30020 .

Inmediatamente aplicamos el operador & a ese objeto. Hay una regla de caso especial que dice que aplicar & al resultado de un operador [] es equivalente a solo hacer la adición del puntero; ver 6.5.3.2p2 en el borrador estándar antes mencionado.

Así que esto:

 &p[b] 

es equivalente a:

 p + b 

que, como dije anteriormente, produce una dirección (de tipo char* ) correspondiente al valor entero 30020 – asumiendo, por supuesto, que las conversiones de entero a puntero se comportan de cierta manera y que el comportamiento indefinido de construir y acceder Un valor de puntero no válido no hace nada sorprendente.

Finalmente, usamos un operador de conversión para convertir esta dirección al tipo int . La conversión de un valor de puntero a un entero también está definida por la implementación, y posiblemente no está definida. Cotizando 6.3.2.3p6:

Cualquier tipo de puntero se puede convertir en un tipo entero. Excepto como se especificó previamente, el resultado está definido por la implementación. Si el resultado no se puede representar en el tipo entero, el comportamiento es indefinido. El resultado no necesita estar en el rango de valores de cualquier tipo de entero.

No es raro que un char* sea ​​más grande que un int (por ejemplo, escribo esto en un sistema con int 32 bits y carácter de 64 bit char* ). Pero en este caso estamos relativamente a salvo del desbordamiento, porque el valor char* es el resultado de convertir un valor int dentro del rango. no hay garantía de que la conversión de un valor dado de int a char* y de vuelta a int dé el resultado original, pero generalmente funciona de esa manera, al menos para los valores que están dentro del rango.

Entonces, si una serie de supuestos específicos de la implementación se satisfacen con la implementación en la que se ejecuta el código, es probable que este código arroje el mismo resultado que 30000 + 20 .

Por cierto, he trabajado en un sistema donde esto hubiera fallado. El Cray T90 era una máquina con dirección de palabra, con direcciones de hardware que apuntaban a palabras de 64 bits; no había soporte de hardware para direccionamiento de bytes. Pero char tenía 8 bits, por lo que los punteros char* y void* tenían que ser construidos y manipulados en hardware. Un puntero char* consistía en un puntero de palabra de 64 bits con un desplazamiento de byte almacenado en los 3 bits de orden superior que de otra manera no se utilizarían. Las conversiones entre punteros y enteros no trataron estos bits de alto orden especialmente; simplemente fueron copiados. Entonces ptr + 1 y (char*)(int)ptr + 1) podrían producir resultados muy diferentes.

Pero bueno, has logrado agregar dos enteros pequeños sin usar el operador + , así que eso es todo.

Una alternativa a la aritmética de punteros es usar bitops:

 #include  #include  unsigned addtwo(unsigned one, unsigned two); unsigned addtwo(unsigned one, unsigned two) { unsigned carry; for( ;two; two = carry << 1) { carry = one & two; one ^= two; } return one; } int main(int argc, char **argv) { unsigned one, two, result; if ( sscanf(argv[1], "%u", &one ) < 1) return 0; if ( sscanf(argv[2], "%u", &two ) < 1) return 0; result = addtwo(one, two); fprintf(stdout, "One:=%u Two=%u Result=%u\n", one, two, result ); return 0; } 

En una nota completamente diferente, tal vez lo que se buscaba era una comprensión de cómo se realiza la adición binaria en hardware, con XOR, Y y desplazamiento de bits. En otras palabras, un algoritmo algo como esto:

 int add(int a, int b) { int partial_sum = a ^ b; int carries = a & b; if (carries) return add(partial_sum, carries << 1); else return partial_sum; } 

O un equivalente iterativo (aunque gcc, al menos, reconoce la función de hoja y optimiza la recursión en una versión iterativa de todos modos; probablemente otros comstackdores también lo harían) ...

Probablemente necesite un poco más de estudio para los casos negativos, pero al menos esto funciona para números positivos.

 /* by sch. 001010101 = 85 001000111 = 71 --------- 010011100 = 156 */ #include  #define SET_N_BIT(i,sum) ((1 << (i)) | (sum)) int sum(int a, int b) { int t = 0; int i = 0; int ia = 0, ib = 0; int sum = 0; int mask = 0; for(i = 0; i < sizeof(int) * 8; i++) { mask = 1 << i; ia = a & mask; ib = b & mask; if(ia & ib) if(t) { sum = SET_N_BIT(i,sum); t = 1; /*i(1) t=1*/ } else { t = 1; /*i(0) t=1*/ } else if (ia | ib) if(t) { t = 1; /*i(0) t=1*/ } else { sum = SET_N_BIT(i,sum); t = 0; /*i(1) t=0*/ } else if(t) { sum = SET_N_BIT(i,sum); t = 0; /*i(1) t=0*/ } else { t = 0; /*i(0) t=0*/ } } return sum; } int main() { int a = 85; int b = 71; int i = 0; while(1) { scanf("%d %d", &a, &b); printf("%d: %d + %d = %d\n", ++i, a, b, sum(a, b)); } return 0; }