Может ли кто-нибудь объяснить логику добавления 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;
}