Может ли кто-нибудь объяснить логику добавления a и b?
#include <stdio.h>
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;
}
Может ли кто-нибудь объяснить логику добавления a и b?
#include <stdio.h>
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;
}
Здесь + скрывается:
&p[b]
это выражение эквивалентно
(p + b)
Итак, у нас есть:
(int) &p[b] == (int) ((char *) a)[b]) == (int) ((char *) a + b) == a + b
Обратите внимание, что это технически вызывает поведение undefined, поскольку (char *) a должно указывать на арифметику объекта и указателя вне объекта или один за объектом, вызывающим поведение undefined.
Стандарт C говорит, что E1[E2] эквивалентен *((E1) + (E2)). Поэтому:
&p[b] = &*((p) + (b)) = ((p) + (b)) = ((a) + (b)) = a + b
p[b] - это b-й элемент массива p. Это как писать *(p + b).
Теперь, добавляя &, это будет похоже на запись: p + b * sizeof(char), которая равна p + b.
Теперь у вас будет (int)((char *) a + b), который... a + b.
Но если у вас еще есть + на клавиатуре, используйте его.
Как пояснил @gerijeshchauhan в комментариях, * и & являются обратными операциями, они отменяют друг друга. Итак, &*(p + b) p + b.
p делается указателем на char
a преобразуется в указатель на char, тем самым делая p точкой в памяти с адресом a
Затем индексный оператор используется для перехода к объекту со смещением b за адресом, на который указывает p. b равно 20 и p + 20 = 30020. Затем адресный оператор используется для результирующего объекта для преобразования адреса обратно в int, и вы получили эффект + b
Ниже могут быть проще следовать следующим комментариям:
#include <stdio.h>
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;
}
Ссылка: здесь
char *p;
p - это указатель (на элемент размером 1 байт)
p=(char *)a;
теперь p указывает на память с адресом a
sum= (int)&p[b];
p указатель может использоваться как массив p[] (начальный адрес (в памяти) этого массива a)
p[b] означает получить b-й элемент - этот адрес элемента a+b
[(начальный адрес) a + b (b-й элемент * размер элемента (1 байт))]
&p[b] означает получить адрес элемента в p[b], но его адрес a+b
если вы используете указатель на int (в основном 4 байта)
int* p
p = (int*)a;
ваша сумма будет a + (4 * b)
int a=30000, b=20, sum;
char *p; //1. p is a pointer to char
p = (char *) a;
a имеет тип int и имеет значение 30000. Вышеприведенное назначение преобразует значение 30000 из int в char* и сохраняет результат в p.
Семантика преобразования целых чисел в указатели (частично) определяется стандартом C. Цитируя проект N1570, раздел 6.3.2.3, пункт 5:
Целое число может быть преобразовано в любой тип указателя. Кроме того, как ранее указано, результат определяется реализацией, может и не быть правильно выровненный, может не указывать на объект ссылочной тип и может быть ловушечным представлением.
с (ненормативной) сноской:
Функции отображения для преобразования указателя на целое число или целое число с указателем предназначены для согласования с адресацией структура среды выполнения.
Стандарт не дает никаких гарантий относительно относительных размеров типов int и char*; либо может быть больше, чем другой, и конверсия может потерять информацию. Результат этих конкретных преобразований вряд ли будет действительным значением указателя. Если это представление ловушки, то поведение присваивания undefined.
В типичной системе, которую вы, вероятно, будете использовать, char* по крайней мере такой же большой, как int, а конверсии целого-на-указателя, вероятно, просто переинтерпретируют биты, составляющие целочисленное представление, как представление значение указателя.
sum = (int)&p[b];
p[b] по определению эквивалентен *(p+b), где + обозначает арифметику указателя. Так как указатель указывает на char, а a char по определению 1 байт, добавление увеличивает адрес в виде b байтов в памяти (в данном случае 20).
Но p, вероятно, не является допустимым указателем, поэтому любая попытка выполнить арифметику на нем или даже получить доступ к его значению имеет поведение undefined.
На практике большинство компиляторов C генерируют код, который не выполняет дополнительные проверки. Акцент делается на быстрое выполнение правильного кода, а не на обнаружение неправильного кода. Поэтому, если предыдущее назначение p установило его на адрес, соответствующий номеру 30000, то добавление b или 20 к этому адресу, вероятно, приведет к адресу, соответствующему номеру 30020.
Этот адрес является результатом (p+b); теперь оператор [] неявно применяет оператор * к этому адресу, предоставляя вам объект, на который указывает этот адрес - концептуально, это объект char, хранящийся по адресу, соответствующему целому числу 30020.
Мы немедленно применяем оператор & к этому объекту. Там правило особого случая, в котором говорится, что применение & к результату оператора [] эквивалентно простому добавлению указателя; см. 6.5.3.2p2 в вышеупомянутой стандартной черновике.
Итак, это:
&p[b]
эквивалентно:
p + b
который, как я сказал выше, дает адрес (типа char*), соответствующий целочисленному значению 30020 - при условии, конечно, что преобразования целых-на-указателей ведут себя определенным образом и что undefined поведение построения и доступа к недопустимому значению указателя не вызывает ничего удивительного.
Наконец, мы используем оператор трансляции для преобразования этого адреса в тип int. Преобразование значения указателя в целое число также определяется реализацией и, возможно, undefined. Цитирование 6.3.2.3p6:
Любой тип указателя может быть преобразован в целочисленный тип. Кроме того, ранее указанный, результат определяется реализацией. Если результат не может быть представлен в целочисленном типе, поведение undefined. Результат не должен находиться в диапазоне значений любых целочисленный тип.
Это не редкость для char* больше, чем int (например, я набираю это в системе с 32-разрядными int и 64-разрядными char*). Но в этом случае мы относительно безопасны от переполнения, потому что значение char* является результатом преобразования значения int в диапазоне. нет гарантии, что преобразование заданного значения из int в char* и обратно в int приведет к исходному результату, но обычно это работает, по крайней мере, для значений, находящихся в диапазоне.
Таким образом, если ряд реализационных предположений будет выполняться реализацией, на которой выполняется код, то этот код, вероятно, даст тот же результат, что и 30000 + 20.
Кстати, я работал над системой, где это было бы неудачно. Cray T90 был машиной с адресами, с аппаратными адресами, указывающими на 64-битные слова; не было аппаратной поддержки для байтовой адресации. Но char был 8 бит, поэтому char* и void* указатели должны были быть сконструированы и обработаны на аппаратном уровне. Указатель char* состоял из 64-битного указателя слова с смещением байта, хранящегося в неиспользуемых 3 разрядах высокого порядка. Конверсии между указателями и целыми числами не рассматривались специально для этих старших бит; они просто были скопированы. Таким образом, ptr + 1 и (char*)(int)ptr + 1) могут давать очень разные результаты.
Но эй, вам удалось добавить два небольших целых числа без использования оператора +, так что это.
Альтернативой арифметике указателя является использование битов:
#include <stdio.h>
#include <string.h>
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;
}
В совершенно другой заметке, возможно, именно то, что искали, это понимание того, как выполняется бинарное добавление в аппаратном обеспечении, с XOR, AND и смещением бит. Другими словами, алгоритм выглядит примерно так:
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;
}
Или итеративный эквивалент (хотя, gcc, по крайней мере, распознает функцию листа и в любом случае оптимизирует рекурсию в итеративную версию, возможно, другие компиляторы тоже)....
Вероятно, нужно немного больше изучить отрицательные случаи, но это, по крайней мере, работает для положительных чисел.
/*
by sch.
001010101 = 85
001000111 = 71
---------
010011100 = 156
*/
#include <stdio.h>
#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;
}