Я немного разбирался в указателях на C, и меня немного смутило немного кода, с которым я столкнулся. Я делал викторину на qeeksquiz.com/pointers
для просмотра, и я наткнулся на этот фрагмент кода:
#include<stdio.h>
int main()
{
int a;
char *x;
x = (char *) &a;
a = 512;
x[0] = 1;
x[1] = 2;
printf("%d\n",a);
return 0;
}
Когда я столкнулся с x = (char *) &a
я немного смутился. Я понимаю, что x является указателем, который содержит адрес a, но когда мы назначаем x[0] = 1
и x[1] = 2;
ответ, когда печатается, составляет 513. В ответе говорится о том, как это зависит от того, какую машину мы используем, и о том, как машина little-endian меняет способ чтения в двоичном формате. Я полностью смущен тем, как мы получаем от 512 до 513. Я предполагаю, что это потому, что x [0] = 1, но я не уверен на 100%. Может кто-нибудь помочь объяснить это? Если бы мы назначили x[0] = 2
, какова была бы величина изменения?
Спасибо за помощь!
Искусство ASCII!
Little endian Big endian
+----+----+----+----+ +----+----+----+----+
a = 0x200: | 00 | 02 | 00 | 00 | | 00 | 00 | 02 | 00 |
+----+----+----+----+ +----+----+----+----+
+----+----+----+----+ +----+----+----+----+
x[0] = 1: | 01 | 02 | 00 | 00 | | 01 | 00 | 02 | 00 |
+----+----+----+----+ +----+----+----+----+
+----+----+----+----+ +----+----+----+----+
x[1] = 2: | 01 | 02 | 00 | 00 | | 01 | 02 | 02 | 00 |
+----+----+----+----+ +----+----+----+----+
result: 1x1 + 2x256 = 513 1x16777216 + 1x65536 + 2x256 + 0x1 = big
поскольку x является указателем на char, это означает, что x [0] и x [1] относятся к однобайтовым данным, поэтому в вашей памяти у вас есть такие данные:
1 2
но во время вывода вы пытаетесь ссылаться на те же данные, что и 16/32 бит, поэтому у нас нет двух одиночных байтов, но 1 слово, которое хранится в памяти как 0x01 0x02, для маленького endian это означает, что мы должны поменять их, поэтому мы получаем число 0x201, которое равно 513 в десятичной нотации
для большого endian будет 0x102, что равно 258 в десятичной
Целое число состоит из последовательности байтов. Но порядок байтов в разных системах различен. Например, рассмотрите номер 134480385 (двоичный = 00001000000001000000001000000001). В маленькой системе, это (с наименьшим адресом на LEFT)
00000001 00000010 00000100 00001000
Но в системе большого конца байты хранятся в обратном порядке. LEFT по-прежнему является самым низким адресом.
00001000 00000100 00000010 00000001
Когда вы берете адрес целого числа a и бросаете его в указатель char (byte), он указывает на первый байт в целочисленном (самый низкий адрес). Когда вы пишете 1 указателю, младший байт устанавливается на 00000001. Однако char имеет длину только 1 байт, поэтому остальные байты не изменяются. Затем второй байт устанавливается равным 00000010.
В вашем примере 512 в little endian
00000000 00000010
Большой endian более сложный, потому что результат зависит от количества байтов в int. Это обычно 4, но может быть 2 или более. В качестве 2-байтового int 512 в памяти
00000010 00000000
и как 4-байтовый int это
00000000 00000000 00000010 00000000
(Это не имеет значения для маленького endian, так как дополнительные байты - только нули)
После записи 1 в первый байт и 2 во второй байт вы получаете в памяти 4-байтовый маленький конец
00000001 00000010 00000000 00000000
4-байтовый большой endian
00000001 00000010 00000010 00000000
Обратите внимание, что бит в третьем байте все еще есть. Это связано с тем, что мы писали только первые два байта. Третий и четвертый байты не изменяются.
и 2-байтовый большой endian
00000001 00000010
Интерпретация 2 или 4-байтовой памяти (дополнительные нули игнорируются для 2-байтов) как малое число endian как нормальное двоичное число, это
00000000000000000000001000000001 = 513
Интерпретация 4-байтовой памяти как большого конечного числа как нормального двоичного числа
00000001000000100000001000000000 = 16908800
Интерпретация 2-байтной памяти как большого конечного числа как нормального двоичного числа
0000000100000010 = 258
Возможно, я ошибся в своих расчетах, но, надеюсь, вы поняли эту идею. Вот почему вам нужно быть осторожным при кастинге между различными типами указателей.
Как указывали другие, трюк заключается в том, чтобы понять, как ints и символы представлены в памяти. Я написал код C++, который пытается показать вам именно это. Вставьте его в файл, скомпилируйте и запустите; затем измените значения в main
и посмотрите, что произойдет. Здесь код:
#include <stdio.h>
// print one byte as a binary number
void print_binary(unsigned u) {
for (int i = 7; i >= 0; --i)
printf("%d", (u >> i) & 1);
}
// print a number binary representation
template <typename T>
void print_int_binary(T i) {
char *cp = (char*)&i;
for (int i = 0; i < sizeof(T); ++i) {
print_binary(cp[i]);
printf(" ");
}
printf("\n");
}
// show how the variable is represented in memory
template <typename T>
void print_var_binary(const char *name, T t) {
printf("%s is stored as %d bytes:\n", name, (int)sizeof(t));
print_int_binary(t);
}
#define PRINT(a) print_var_binary(#a, a);
int main() {
PRINT((int)513)
PRINT((char)2)
}
Когда я запускаю его на своем компьютере (little-endian), он печатает:
(int)513 is stored as 4 bytes:
00000001 00000010 00000000 00000000
(char)2 is stored as 1 bytes:
00000010
sizeof(int)
?int
. Для 32-битногоint
это будет 0x01020000