Я действительно смущен тем, как работают указатели uint32_t в C ++
Я просто возился с попытками изучить TEA, и я не понял, когда они передали параметр uint32_t в функцию шифрования, затем в функции объявили переменную uint32_t и присвоили ей параметр, как если бы этот параметр был массивом.
как это:
void encrypt (uint32_t* v, uint32_t* k) {
uint32_t v0=v[0], v1=v[1], sum=0, i;
Поэтому я решил поиграться с указателями uint32_t и написал крошечный код
int main ()
{
uint32_t *plain_text;
uint32_t key;
unsigned int temp=123232;
plain_text=&temp;
key=7744;
cout<<plain_text[1]<<endl;
return 0;
}
и это поразило меня, когда на выходе было значение «ключ». Я понятия не имею, как это работает … и затем, когда я попытался с plain_text [0], он вернулся со значением «temp».
Так что я чертовски застрял, пытаясь понять, что происходит.
РЕДАКТИРОВАТЬ: оглядываясь на код TEA, uint32_t * v указывает на массив, а не один беззнаковый int? и что я сделал, был просто счастливой случайностью?
uint32_t
это тип. Это означает 32-разрядное целое число без знака. В вашей системе это, вероятно, имя typedef для unsigned int
,
Нет ничего особенного в указателе на этот конкретный тип; Вы можете иметь указатели на любой тип.
[]
в C и C ++ на самом деле указатель индексации. p[0]
означает получить значение в том месте, на которое указывает указатель. p[1]
получает значение в следующей ячейке памяти после этого. затем p[2]
следующая точка после этого и так далее.
Вы также можете использовать эту запись с массивами, потому что имя массива преобразуется в указатель на его первый элемент при использовании таким образом.
Итак, ваш код plain_text[1]
пытается прочитать следующий элемент после temp
, поскольку temp
на самом деле не массив, это вызывает неопределенное поведение. В вашем конкретном случае проявлением этого неопределенного поведения является то, что ему удалось прочитать адрес памяти после temp
без сбоев, и этот адрес был тот же адрес, где key
хранится.
Формально ваша программа имеет неопределенное поведение.
Выражение plain_text[1]
эквивалентно *(plain_text + 1)
([expr.sub] / 1). Хотя вы можете указывать на один конец конца массива (объекты, которые не являются массивами, по-прежнему считаются одноэлементными массивами для целей арифметики указателей ([expr.unary.op] / 3)), вы не можете разыменовать этот адрес ([expr.unary.op] / 1).
На этом этапе компилятор может делать все, что хочет, в этом случае он просто решил обработать выражение так, как если бы он мы указывая на массив и plain_text + 1
т.е. &temp + 1
указывает на следующее uint32_t
объект в стеке, который в данном случае по совпадению key
,
Вы можете увидеть, что происходит, если вы посмотрите на сборку
mov DWORD PTR -16[rbp], 123232 ; unsigned int temp=123232;
lea rax, -16[rbp]
mov QWORD PTR -8[rbp], rax ; plain_text=&temp;
mov DWORD PTR -12[rbp], 7744 ; key=7744;
mov rax, QWORD PTR -8[rbp]
add rax, 4 ; plain_text[1], i.e. -16[rbp] + 4 == -12[rbp] == key
mov eax, DWORD PTR [rax]
mov edx, eax
mov rcx, QWORD PTR .refptr._ZSt4cout[rip]
call _ZNSolsEj ; std::ostream::operator<<(unsigned int)
mov rdx, QWORD PTR .refptr._ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_[rip]
mov rcx, rax
call _ZNSolsEPFRSoS_E ; std::ostream::operator<<(std::ostream& (*)(std::ostream&))
mov eax, 0
add rsp, 48
pop rbp
ret
В C и C ++ массивы распадаются на указатели, что приводит к эквивалентность массив / указатель.
a[1]
когда a
простой тип эквивалентен
*(a + 1)
Если a
это массив простых типов, a
будет распадаться при первой возможности на адрес элемента 0.
int arr[5] = { 0, 1, 2, 3, 4 };
int i = 10;
int* ptr;
ptr = arr;
std::cout << *ptr << "\n"; // outputs 0
ptr = &arr[0]; // same address
std::cout << *ptr << "\n"; // outputs 0
std::cout << ptr[4] << "\n"; // outputs 4
std::cout << *(ptr + 4) << "\n"; // outputs 4
ptr = &i;
std::cout << *ptr << "\n"; // outputs 10
std::cout << ptr[0] << "\n";
std::cout << ptr[1] << "\n"; // UNDEFINED BEHAVIOR.
std::cout << *(ptr + 1) << "\n"; // UNDEFINED BEHAVIOR.
Чтобы понять ptr[0]
а также ptr[1]
ты просто должен понять арифметика указателей.
uint32_t * plain_text; // в памяти зарезервировано 4 байта простой текст
uint32_t key; // в памяти следующие 4 байта после простой текст зарезервированы для ключ
таким образом: &plain_text [0] представляет собой plain_text и &plain_text [1] относится к
следующие 4 байта, которые находятся в &ключ
Этот сценарий может объяснить такое поведение.