Страницы

Поиск по вопросам

Показаны сообщения с ярлыком указатели. Показать все сообщения
Показаны сообщения с ярлыком указатели. Показать все сообщения

четверг, 9 апреля 2020 г.

Работа перегруженного оператора во время инициализации экземпляра класса [закрыт]

#cpp #классы #указатели #перегрузка_операторов #операторы

                            
             
                
                    
                        
                            Закрыт. Этот вопрос не по теме. Ответы на него в данный
момент не принимаются.
                            
                        
                    
                
            
                    
                
                        
                            
                        
                    
                        
                            Хотите улучшить этот вопрос? Переформулируйте вопрос,
чтобы он соответствовал тематике «Stack Overflow на русском».
                        
                        Закрыт 4 года назад.
                    
                
        

Как перегрузить, например, оператор присваивания таким образом, чтобы он работал
во время инициализации экземпляра класса? 

#include `
using namespace std;`

class Matrix
{
private:
    int **Arr;
    int Size;
public:
    Matrix(int SizeOfMatrix);
Matrix operator = (Matrix &);
};

int main()
{
    Matrix A(4);
    Matrix B(4) = A;
    system("pause");
    return 0;
}
Matrix::Matrix(int SizeOfMatrix)
{
    Size = SizeOfMatrix;
    Arr = new int *[Size];
    for (int i = 0; i < Size; i++)
    {
        Arr[i] = new int[Size];
        for (int j = 0; j < Size; j++)
            Arr[i][j] = rand() % 50;
    }
}
Matrix Matrix :: operator =(Matrix &A)
{
    for (int i = 0; i < Size; i++)
    {
        for (int j = 0; j < Size; j++)
            Arr[i][j] = A.Arr[i][j];
    }
    return *this;
}

    


Ответы

Ответ 1



Прежде всего хотел бы отметить, что данное предложение Matrix B(4) = A; некорректное и не должно компилироваться. Фактически в данном предложении имеется два инициализатора: 4 и A. Но даже если вы правильно напишите Matrix A(4); Matrix B = A; то здесь вызывается не оператор присваивания, как вы думаете, а конструктор копирования, который определен компилятором неявно, и который просто почленно копирует члены данных объектов. Когда вы динамически в классе распределяете память, то есть используете указатели, то вы должны определить явно по крайней мере конструктор копирования, копирующий оператор присваивания и деструктор. В противном случае поведение вашей программы может оказаться неопределенным. Например, в результате использования конструктора копирования, созданного компилятором неявно, может оказаться, что два объекта имеют указатели на одну и ту же область памяти, а потому при удалении этих объектов будет осуществлена попытка удалить одну и ту же память дважды. Ваш класс может выглядеть следующим образом class Matrix { private: int **Arr; size_t Size; public: explicit Matrix( size_t ); Matrix( const Matrix & ); ~Matrix(); Matrix & operator = ( const Matrix & ); }; Matrix::Matrix( size_t SizeOfMatrix ) : Arr( nullptr ), Size ( SizeOfMatrix ) { if ( Size ) { Arr = new int *[Size]; for ( size_t i = 0; i < Size; i++ ) { Arr[i] = new int[Size]; for ( size_t j = 0; j < Size; j++ ) Arr[i][j] = rand() % 50; } } } Matrix::Matrix( const Matrix &rhs ) : Arr( nullptr ), Size ( rhs.Size ) { if ( Size ) { Arr = new int *[Size]; for ( size_t i = 0; i < Size; i++ ) { Arr[i] = new int[Size]; for ( size_t j = 0; j < Size; j++ ) Arr[i][j] = rhs.Arr[i][j]; } } } Matrix::~Matrix() { for ( size_t i = 0; i < Size; i++ ) delete [] Arr[i]; delete [] Arr; } Matrix & Matrix::operator =( const Matrix &rhs ) { if ( this != &rhs ) { int **tmp = nullptr; if ( rhs.Size != 0 ) { if ( Size != rhs.Size ) { tmp = new int *[rhs.Size]; for ( size_t i = 0; i < rhs.Size; i++ ) { tmp[i] = new int[rhs.Size]; } } else { tmp = Arr; } for ( size_t i = 0; i < rhs.Size; i++ ) { for ( size_t j = 0; j < rhs.Size; j++ ) tmp[i][j] = rhs.Arr[i][j]; } } if ( Size != rhs.Size ) { for ( size_t i = 0; i < Size; i++ ) delete [] Arr[i]; delete [] Arr; } Arr = tmp; Size = rhs.Size; } return *this; }

четверг, 19 марта 2020 г.

Поле класса - указатель на метод

#cpp #указатели


Здравствуйте!

В одном из ответов увидел, что поле класса определяется таким образом:

    void(My::*z)( int );


Поясните, пожалуйста такую запись. В каких случаях она используется? 
    


Ответы

Ответ 1



В каких случаях она используется? Такое поле используется в случаях, когда есть какая-то внешняя зависимость на метод класса. В моем примере эта зависимость от внутреннего состояния объекта. Без "указателя на метод" все равно пришлось бы вводить дополнительную переменную State. Но в таком случае, логика метода была бы не так очевидна. Вот тот же пример без z: class My{ int State; public: My( void ) : State( 0 ){} void state( int x ){ switch( State ){ case 0: if( x == 1 ){ State = 1; } break; case 1: if( x == 2 ){ State = 2; }else{ State = 1; } break; case 2: if( x == 3 ){ State = 0; } break; } } } Очевидно, в таком варианте запутаться в номерах состояний гораздо проще, чем в исходном. Для лучшей ориентации по коду в таких случаях вводят enum: class My{ enum{ state_1 = 0, state_2, state_3 }; int State; public: My( void ) : State( state_1 ){} void state( int x ){ switch( State ){ case state_1: if( x == 1 ){ State = state_2; } break; case state_2: if( x == 2 ){ State = state_3; }else{ State = state_1; } break; case 2: if( x == 3 ){ State = state_1; } break; } } } Но на мой взгляд, даже в таком случае исходный вариант выгоднее для понимания.

воскресенье, 15 марта 2020 г.

Функция memset_word делает пропуск между словами

#c #указатели #память #memory


Пока писал учебную ОС, пришлось в качестве одной из функций стандартной библиотеки
написать функцию memset_word. Проблема была тут же решена "в лоб":

    void memset_word(uint16_t* mem, uint16_t value, size_t count) {
        uint16_t* addr;
        for(addr = mem; addr < mem + count * sizeof(uint16_t);
                addr += sizeof(uint16_t)) {
            *addr = value;
        }
    }


Но у этой функции было обнаружено неприятное свойство: она записывает слова через
одно. Другими словами, память была такая:

00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00,

а после вызова

    memset((uint16_t*) 0, 0xCDAB, 3)


стала такая:

AB CD 00 00 AB CD 00 00 AB CD 00 00 00 00 00 00 00 00 00 00,

хотя должна была стать такой:

AB CD AB CD AB CD 00 00 00 00 00 00 00 00 00 00 00 00 00 00.

В принципе, помогает замена addr += sizeof(uint16_t) на addr++, но я не понимаю:
ведь указатели в C - настоящие адреса, так почему же для того, чтобы перейти к следующему
слову, нужно прибавлять 1, а не размер слова? Или же ошибка кроется где-то в коде функции?
Прошу объяснить мне это.
    


Ответы

Ответ 1



У вас эта функция void memset_word(uint16_t* mem, uint16_t value, size_t count) { uint16_t* addr; for(addr = mem; addr < mem + count * sizeof(uint16_t); addr += sizeof(uint16_t)) { *addr = value; } } некорректная. В ней неправильно используется арифметика указателей. Я думаю, вы имели в виду следующее void memset_word(uint16_t* mem, uint16_t value, size_t count) { for(uint16_t *addr = mem; addr != mem + count; ++addr ) *addr = value; } } Вы должны перейти к следующему объекту. Увеличение указателя на 1 увеличивает его адрес на sizeof( uint16_t ). В этом состоит принцип работы оператора индексирования, когда вы пишите, например, mem[1] что эквивалентно выражению *(mem + 1) и в виду коммутативности операции сложения вы можете также записать 1[mem]

Ответ 2



Я бы сказал не так. Арифметика указателей верная, и Ваша функция работала бы правильно, если бы архитектура машины была 16-разрядная. В 32-разрядных машинах размер слова равен 4 байтам. При этом все числа (кроме char), размер которых меньше 4 байт, аппаратно выравниваются по 4-байтному слову. Другими словами, нельзя (не рекомендуется) разместить два 16-разрядных слова в одном 4-байтном аппаратном слове. В случае, если вы запишете подряд два 16-разрядных слова, процессору придётся осуществлять больше операций, чтобы извлечь его из оперативной памяти и записать обратно. Нужно извлечь 4 байта (1 инструкция), далее в зависимости от того, младшее это слово или старшее (сравнение, условный переход - 2 инструкции) осуществляется наложение маски 0xFFFF0000 или 0x0000FFFF соответственно (2 инструкции). Если это старшее слово, то осуществляется сдвиг на 16 бит (1 инструкция). Все эти инструкции осуществляются аппаратно. Итого извлечение 16-разрядного полуслова из 32-разрядного слова тяжелее в 6 раз (приблизительно). Поэтому решите, под какую архитектуру вы пишете и какой размер слова вам нужен.

Свой List из array

#cpp #массивы #классы #указатели #список


Пытаюсь создать свой list из array

есть структура 

template 
struct Node {
    T data;
    Node* next; 
};


собственно функция 

template 
Node* arrayToList(const T tab[], size_t size){

Node *node = new Node;
for(int i=0;idata = tab[i];
node->next = new Node;
}
}


в main 

    int tabi[] = {2,1,4,3,6,5,7,8};
size_t sizei = sizeof(tabi)/sizeof(tabi[0]);
Node *listAi = arrayToList(tabi,sizei);


так вот проблема в том что не могу править созданный экземпляр Node тут node->next
= new Node; я создаю экземпляр , а как в него значение из массива занести ?  
    


Ответы

Ответ 1



Не понятно, в чем состоит ваша проблема Поэтому я предложу код, который позволяет заполнить список элементами массива. Надеюсь, что если это не то, что вам нужно, то вы об этом сообщите мне в своем комментарии к ответу. Вот демонстрационная программа #include template struct Node { T data; Node *next; }; template Node * arrayToList( const T a[], size_t n ) { Node *head = nullptr; Node **current = &head; for ( size_t i = 0; i < n; i++, current = &( *current )->next ) { *current = new Node { a[i], nullptr }; } return head; } template void displayList( Node *head ) { for ( ; head; head = head->next ) std::cout << head->data << ' '; } int main() { int a[] = { 2, 1, 4, 3, 6, 5, 7, 8 }; const size_t N = sizeof( a ) / sizeof( *a ); for ( int x : a ) std::cout << x << ' '; std::cout << std::endl; Node *list = arrayToList( a, N ); displayList( list ); std::cout << std::endl; } Ее вывод на консоль: 2 1 4 3 6 5 7 8 2 1 4 3 6 5 7 8 Если ваш компилятор не поддерживает список инициализации для оператора new, то предложение *current = new Node { a[i], nullptr }; вы можете заменить на следующие предложения *current = new Node; ( *current )->data = a[i]; ( *current )->next = nullptr; Либо вы можете написать для класса Node конструктор, чтобы можно было бы опять записать все в одну строчку, как, например *current = new Node( a[i], nullptr );

воскресенье, 8 марта 2020 г.

Что значат операторы @ и ^ в Делфи?

#delphi #указатели


Что значат операторы @ и ^ ?? Так же хочется узнать, что будет в Pkts если Psh[4, 1] = ?

Blocks = 4; 
RTParts = 2; 
BlNum, ROTO: integer
mkts = array [0..3, 1..6, 1..16] of SmallInt;
msh = array[1..392] of SmallInt;
TPmkts = ^mkts;
TPmsh = ^msh;
Pkts: array[1..Blocks, 1..RTParts] of TPmkts = ( (nil, nil), (nil, nil), (nil, nil),
(nil, nil) );
Psh: array[1..Blocks, 1..RTParts] of TPmsh = ( (nil, nil), (nil, nil), (nil, nil),
(nil, nil) );

Pkts[Num, ROTO]:= @Psh[Num, ROTO]^[9];

    


Ответы

Ответ 1



Оператор @ обозначает взятие адреса переменной (получение указателя на переменную) Оператор ^ в коде обозначает разыменование указателя и получение значения переменной. Оператор ^ при объявлении типа обозначает использование указателя. Например PPoint = ^TPoint обозначает, что объект типа PPoint будет указателем на объект типа TPoint. Эти операторы дополняют друг друга. var a: integer; b: pointer; begin a := 123; // теперь b указывает на a b := @a; // приводим указатель к конкретному типу и записываем в адрес, // на который указывает b - новое значение 120 // теперь a будет 120 PInteger(b)^ := 120; end; что будет в Pkts если Psh[4, 1] = 230 ? Pkts[4, 1]:= @Psh[4, 1]^[9]; В Pkts[4,1] будет элемент со смещением 9 от Psh[4,1]. Давайте разберем по шагам, мы получаем адрес переменной Psh[4,1], тут же его разыменовываем обратно в переменную, и от этой переменной берем [9] элемент (то есть похоже что это строка или другой массив). Обратите внимание, что подобный код не скомпилируется в Делфи, т.к. не указан разыменуемый тип @Psh[4, 1].

Почему без ошибок выполняется этот код?

#c #указатели #calloc #строковый_литерал


char * buf = (char*)calloc(0,0);

    buf = "vygbubinon";

    printf("%s\n", buf);


Выводится "vygbubinon". Хотя по идее памяти выделено недостаточно
    


Ответы

Ответ 1



Давайте последовательно рассмотрим предложения указанного вами фрагмента кода. В первом предложении char * buf = (char*)calloc(0,0); выделяется память нулевого размера. Согласно стандарту языка C (7.22.3 Memory management functions) If the size of the space requested is zero, the behavior is implementation-defined: either a null pointer is returned, or the behavior is as if the size were some nonzero value, except that the returned pointer shall not be used to access an object. То есть вы можете запрашивать выделение памяти нулевого размера, но результат будет зависеть от платформы, где компилируется и выполняется программа. Либо будет возвращено значение NULL, либо некоторый действительный адрес памяти, по которому однако вы не можете обращаться к объекту. Так что данное предложение корректно. Далее в следующем предложении независимо от того, каков был результат предыдущего предложения (либо NULL, либо некоторый действительный адрес) указатель buf переприсваивается, и получает адрес первого символа строкового литерала "vygbubinon". buf = "vygbubinon"; Строковые литералы имеют статическую память. Следовательно, указатель получает адрес первого символа данного строкового литерала, расположенного в статической области памяти. Память для строковых литералов резервируется компилятором на этапе компиляции. (Компиляторы обычно собирают все литералы, присутствующее в программе, в некоторый пул литералов.) Ничего некорректного в этом предложении нет. Единственная проблема, связанная с этим предложением состоит в том, что если в предыдущем предложении была выделена память, то вы уже не сможете ее удалить, так как адрес этой памяти за счет переприсвоения указателя был утерян. Произойдет утечка памяти, но никакой ошибки времени компиляции или времени выполнения не будет. Ну, и, наконец, в этом предложении выводится на консоль данный литерал. printf("%s\n", buf); Так что фрагмент кода совершенно корректный за исключением возможной утечки памяти, что представляет собой лишь логическую ошибку.

Ответ 2



памяти выделено недостаточно Нет, достаточно. Вот этот код: buf = "vygbubinon"; не копирует строку в память, на которую указывает buf. Он всего лишь присваивает указателю buf адрес строки "vygbubinon", лежащей где-то в сегменте данных. Если будет понятней, то на уровень ниже это может выглядеть как-то так: .data ; ... _some_static_string db "vygbubinon", 0 buf dd ? ; ... .code ; ... mov buf, offset _some_static_string Если же вам нужно скопировать строку в область памяти, адрес которой содержится в указателе, нужно использовать функции семейств strcpy(), memcpy() и другие.

Ответ 3



char *buf - всего-лишь переменная-указатель. По сути она является просто адресом некоторой ячейки памяти. В случае со строками - это адрес самого первого символа. Поэтому в первой строке вы присваиваете переменной buf адрес выделенной памяти, но никак не саму память. А в следующей строке присваиваете buf значение из области данных программы, т.е. ваше "vygbubinon". При этом следует учитывать, что если вы не освобождаете кусок памяти, выделенный в куче вызовом malloc (если вы выделяли не 0) и перезаписываете указатель на нее, то происходит утечка памяти

четверг, 5 марта 2020 г.

Буфер — это обязательно Heap?

#winapi #ассемблер #указатели


Здравствуйте, скажите пожалуйста, вот во многих (по крайне мере которые я встречал)
строковых-winapi'шных функциях следует передавать указатель на некий буфер, буфер обязательно
должен быть заранее выделенной кучей (Heap)? Можно отправить указатель например на
заранее зарезервированный массив байт?
    


Ответы

Ответ 1



Лишь бы эта память была корректна во время работы с ней (ну и, конечно же, доступна для записи-чтения :)). Например, при выделении памяти в стеке следует не забывать, что она будет корректна только до окончания вызова функции. А так - что выделите, то и годится :)

среда, 4 марта 2020 г.

проблемы с памятью

#c #указатели #память #динамические_массивы


Доброго времени суток! Приведу пример кода, в котором укажу лишь те места, где выделяется
непосредственно память. Заранее удалю все места, где я эту память освобождаю, чтобы
вы, как более опытные программисты в Си, чем я, начинающий, указали мне, как лучше
следует с ней обращаться, а главное - показали, из-за чего всё-таки у меня программа
в процессе выполнения вылетает с ошибкой .exe вызвал срабатывание точки останова.

Итак, сам код:

    void func(int *intArray, int mode, int length, int **p, int **hufTemp, 
    int *num)
    {
    int *intPtrTemp;
    int *intListTemp, *intListIndexes;
    int *intPtrLength, *intPtrLength_Temp;
    int *intPtrBlCount; 
    int *intPtrNextCode;
    int *intPtrHuffmanTree;
    int *intDictHuffmanTree;
    int count = 0 , max = 0;



    intPtrTemp = (int*)malloc(length * sizeof(int));
    memset(intPtrTemp, 0, length * sizeof(int));
    memcpy(intPtrTemp, intArray, length * sizeof(int));

    // процесс вычисления count
    intListTemp = (int*)malloc(count * sizeof(int));
    intListIndexes = (int*)malloc(count * sizeof(int));
    memset(intListTemp, 0, count * sizeof(int));
    memset(intListIndexes, 0, count * sizeof(int));

    intPtrLength = (int*)malloc(count * sizeof(int));
    memset(intPtrLength, 0, count * sizeof(int));
    memcpy(intPtrLength, intListTemp, count * sizeof(int));

    intPtrLength_Temp = (int*)malloc(count * sizeof(int));
    memset(intPtrLength_Temp, 0, count * sizeof(int));
    memcpy(intPtrLength_Temp, intPtrLength, count * sizeof(int));

    // процесс вычисления max
    memcpy(intPtrLength_Temp, intPtrLength, count * sizeof(int));
    intPtrBlCount = (int*)malloc((max + 1)*sizeof(int));
    memset(intPtrBlCount, 0, (max + 1) * sizeof(int));

    intPtrNextCode = (int*)malloc((max + 1) * sizeof(int));
    memset(intPtrNextCode, 0, (max + 1) * sizeof(int));

    intPtrHuffmanTree = (int*)malloc(count * sizeof(int));
    memset(intPtrHuffmanTree, 0, count * sizeof(int));

    intDictHuffmanTree = (int*)malloc(count * sizeof(int));
    memset(intDictHuffmanTree, 0, count * sizeof(int));

    memcpy(*p, intPtrLength, count * sizeof(int));
    memcpy(*hufTemp, intDictHuffmanTree, count * sizeof(int));

    *num = count;
}
void main()
{
    char **dictFirstHuffmanTree;
    int intHuffmanTree_Init[19];
    int *p, *_intHuffmanTree, count = 0;

    // вычисление элементов массива intHuffmantree_Init
    p = (int*)malloc(sizeof(int));
    _intHuffmanTree = (int*)malloc(sizeof(int));

    func(intHuffmanTree_Init, 1, 19, &p, &_intHuffmanTree, &count);
    dictFirstHuffmanTree = (char**)malloc(count * sizeof(char*));
    getchar();
}


Пара важных моментов.
1. Функция содержится в файле test1.c. Дёргаю я её из файла test2.c. 
2. Если всё писать в одном файле, вот так как я представил (т.е. без освобождения
памяти), то всё прекрасно работает.
    


Ответы

Ответ 1



Как минимум: p = (int*)malloc(sizeof(int)); Теперь p указывает на блок в 4 байта (ну, чтоб не писать sizeof(int), пусть 32-разрядная программа). Передаете его в функцию. Значение p не меняется, теперь это *p - раз в функцию передан адрес p. И в этот блок вы копируете незнамо сколько памяти: memcpy(*p, intPtrLength, count * sizeof(int)); Если count больше 1, вы выходите за рамки выделенного блока и перезаписываете служебные структуры менеджера памяти. Я не говорю, что это единственная ошибка, но дальше я не смотрю - пока что нет смысла... И вы как, ничего не освобождаете выделенного? Устраиваете себе утечку памяти?

понедельник, 24 февраля 2020 г.

Будет ли копироваться значение при передаче умных указателей в функцию или будет передаваться ссылка?

#cpp #указатели


Например, имеется некоторый класс:

class CSomeClass
{
    double GetLengthLine(std::shared_ptr firstVertex, std::shared_ptr
secondVertex) const;
}




double CTriangle::GetLengthLine(std::shared_ptr firstVertex, std::shared_ptr
secondVertex) const
{
    ....
    return std::hypot(dx, dy);
};


Будет ли копироваться значение при передаче умных указателей в функцию, или будет
передаваться ссылка?
    


Ответы

Ответ 1



Если я правильно понял вопрос (хотя на этот счет есть сомнение, т.к. уже имеется принятый ответ), то речь всё же о копировании объекта типа CPoint, которым параметризован std::shared_ptr. В этом случае совершенно не важно как будет передаваться объект умного указателя по ссылке или по значению - это не приведет к дополнительному копированию объекта, хранимого в указателе. Пример: #include #include struct S { S() { std::cout << "ctor\n"; } S(const S&) { std::cout << "copy\n"; } S(S&&) { std::cout << "move\n"; } }; void f(std::shared_ptr) {} void g(const std::shared_ptr&) {} int main(){ auto s = std::make_shared(); f(s); g(s); } Вывод: ctor Т.е. был создан только один объект. Ни копирований, ни перемещений не произошло. Это в принципе логично, т.к. подобным же образом не происходит создания (полезных) объектов при передаче обычных (не умных) указателей. В общем случае, любой достаточно большой (более нескольких sizeof(int)) объект имеет смысл передавать по константной ссылке, если не предполагается его модифицировать.

Ответ 2



Так как параметры ваших функций принимают объект типа std::shared_ptr по значению, то в функции будут передаваться копии аргументов, с которыми они вызваны. То есть соответствующий аргумент либо будет перемещаться, либо копироваться в параметр функции, так как класс std::shared_ptr имеет как конструктор копирования, так и конструктор перемещения. Но будет меняться число ссылок на тот указатель, который обернут в std::shared_ptr. То есть при вызове функций будет меняться счетчик ссылок на исходный "сырой" указатель. Рассмотрите следующую демонстрационную программу. #include #include void f( std::shared_ptr p ) { std::cout << "Inside f() shared_ptr::use_count() = " << p.use_count() << std::endl; } int main() { std::shared_ptr p( new int ( 10 ) ); std::cout << "Before calling f shared_ptr::use_count() = " << p.use_count() << std::endl; f( p ); std::cout << "Aftera calling f shared_ptr::use_count() = " << p.use_count() << std::endl; return 0; } Ее вывод на консоль следующий Before calling f shared_ptr::use_count() = 1 Inside f() shared_ptr::use_count() = 2 After calling f shared_ptr::use_count() = 1

пятница, 21 февраля 2020 г.

Вопрос по указателям в Golang

#golang #указатели #типы_данных


Недавно начал изучать язык программирования Golang, не очень по началу хотелось,
но по работе нужно было, после программирования на Java чуток сложновато.
Появилось пару вопросов, в языке Golang простые типы передаются по значению, так
же к простому типу относятся и массивы, maps и slices передаются по ссылке, структуры
я понял можно передавать как по ссылке так и по значению, все зависит от разработчика,
но часто натыкаюсь на примеры в нете где слайсы и мапы передают через взятие адреса,
зачем это нужно, если по умолчанию, они все равно так и возвращаются, тем более если
передать слайс явно по ссылке то его уже нельзя итерировать, если я что-то не правильно
понял объясните, очень мало документации на русском.
    


Ответы

Ответ 1



В golang нет передачи параметров по ссылке - параметры всегда передаются по значению, т.е. копируются. Оператор & - это взятие адреса переменной, т.е. передача указателя, а не передача параметра по ссылке, т.е. как частный случай по значению передаются указатели. map, slice - тоже передаются по значению, просто у них внутри указатели и происходит копирование указателей, а не содержимого таблицы/среза. Проще всего это продемонстрировать на примере slice (среза). Внутри slice представляет собот структуру: https://github.com/golang/go/blob/master/src/runtime/slice.go type slice struct { array unsafe.Pointer len int cap int } т.е. при передаче по значению - можно изменять элементы внутри slice.array, т.к. указатель после копирования будет указывать на ту же область памяти что и исходный, но вот изменить длину или вместимость срезу уже не получится. Точнее эти изменения не будут видны снаружи вызываемой функции, т.к. длина, вместимость и (возможно) указатель array изменятся только в копии среза. Вот наглядный пример: https://play.golang.org/p/SpFFDdzeXR package main import ( "fmt" ) func f_slice_item(s []int) { s[0] = 1 } func f_slice_size(s []int) { s = append(s, 1) } func f_slice_pointer_item(s *[]int) { (*s)[0] = 3 } func f_slice_pointer_size(s *[]int) { *s = append(*s, 5) } func f_iter(s *[]int) { for i, v := range *s { fmt.Println(i, "-", v) } } func main() { var s []int fmt.Println("slice by value") s = []int{0} f_slice_item(s) fmt.Println(s) s = []int{0} f_slice_size(s) fmt.Println(s) fmt.Println() fmt.Println("slice by pointer") s = []int{0} f_slice_pointer_item(&s) fmt.Println(s) s = []int{0} f_slice_pointer_size(&s) fmt.Println(s) fmt.Println() fmt.Println("iter") f_iter(&s) } Результат выполнения: slice by value [1] [0] slice by pointer [3] [0 5] iter 0 - 0 1 - 5

воскресенье, 16 февраля 2020 г.

Будет ли копироваться значение при передаче умных указателей в функцию или будет передаваться ссылка?

#cpp #указатели


Например, имеется некоторый класс:

class CSomeClass
{
    double GetLengthLine(std::shared_ptr firstVertex, std::shared_ptr
secondVertex) const;
}




double CTriangle::GetLengthLine(std::shared_ptr firstVertex, std::shared_ptr
secondVertex) const
{
    ....
    return std::hypot(dx, dy);
};


Будет ли копироваться значение при передаче умных указателей в функцию, или будет
передаваться ссылка?
    


Ответы

Ответ 1



Если я правильно понял вопрос (хотя на этот счет есть сомнение, т.к. уже имеется принятый ответ), то речь всё же о копировании объекта типа CPoint, которым параметризован std::shared_ptr. В этом случае совершенно не важно как будет передаваться объект умного указателя по ссылке или по значению - это не приведет к дополнительному копированию объекта, хранимого в указателе. Пример: #include #include struct S { S() { std::cout << "ctor\n"; } S(const S&) { std::cout << "copy\n"; } S(S&&) { std::cout << "move\n"; } }; void f(std::shared_ptr) {} void g(const std::shared_ptr&) {} int main(){ auto s = std::make_shared(); f(s); g(s); } Вывод: ctor Т.е. был создан только один объект. Ни копирований, ни перемещений не произошло. Это в принципе логично, т.к. подобным же образом не происходит создания (полезных) объектов при передаче обычных (не умных) указателей. В общем случае, любой достаточно большой (более нескольких sizeof(int)) объект имеет смысл передавать по константной ссылке, если не предполагается его модифицировать.

Ответ 2



Так как параметры ваших функций принимают объект типа std::shared_ptr по значению, то в функции будут передаваться копии аргументов, с которыми они вызваны. То есть соответствующий аргумент либо будет перемещаться, либо копироваться в параметр функции, так как класс std::shared_ptr имеет как конструктор копирования, так и конструктор перемещения. Но будет меняться число ссылок на тот указатель, который обернут в std::shared_ptr. То есть при вызове функций будет меняться счетчик ссылок на исходный "сырой" указатель. Рассмотрите следующую демонстрационную программу. #include #include void f( std::shared_ptr p ) { std::cout << "Inside f() shared_ptr::use_count() = " << p.use_count() << std::endl; } int main() { std::shared_ptr p( new int ( 10 ) ); std::cout << "Before calling f shared_ptr::use_count() = " << p.use_count() << std::endl; f( p ); std::cout << "Aftera calling f shared_ptr::use_count() = " << p.use_count() << std::endl; return 0; } Ее вывод на консоль следующий Before calling f shared_ptr::use_count() = 1 Inside f() shared_ptr::use_count() = 2 After calling f shared_ptr::use_count() = 1

Вопрос по указателям в Golang

#golang #указатели #типы_данных


Недавно начал изучать язык программирования Golang, не очень по началу хотелось,
но по работе нужно было, после программирования на Java чуток сложновато.
Появилось пару вопросов, в языке Golang простые типы передаются по значению, так
же к простому типу относятся и массивы, maps и slices передаются по ссылке, структуры
я понял можно передавать как по ссылке так и по значению, все зависит от разработчика,
но часто натыкаюсь на примеры в нете где слайсы и мапы передают через взятие адреса,
зачем это нужно, если по умолчанию, они все равно так и возвращаются, тем более если
передать слайс явно по ссылке то его уже нельзя итерировать, если я что-то не правильно
понял объясните, очень мало документации на русском.
    


Ответы

Ответ 1



В golang нет передачи параметров по ссылке - параметры всегда передаются по значению, т.е. копируются. Оператор & - это взятие адреса переменной, т.е. передача указателя, а не передача параметра по ссылке, т.е. как частный случай по значению передаются указатели. map, slice - тоже передаются по значению, просто у них внутри указатели и происходит копирование указателей, а не содержимого таблицы/среза. Проще всего это продемонстрировать на примере slice (среза). Внутри slice представляет собот структуру: https://github.com/golang/go/blob/master/src/runtime/slice.go type slice struct { array unsafe.Pointer len int cap int } т.е. при передаче по значению - можно изменять элементы внутри slice.array, т.к. указатель после копирования будет указывать на ту же область памяти что и исходный, но вот изменить длину или вместимость срезу уже не получится. Точнее эти изменения не будут видны снаружи вызываемой функции, т.к. длина, вместимость и (возможно) указатель array изменятся только в копии среза. Вот наглядный пример: https://play.golang.org/p/SpFFDdzeXR package main import ( "fmt" ) func f_slice_item(s []int) { s[0] = 1 } func f_slice_size(s []int) { s = append(s, 1) } func f_slice_pointer_item(s *[]int) { (*s)[0] = 3 } func f_slice_pointer_size(s *[]int) { *s = append(*s, 5) } func f_iter(s *[]int) { for i, v := range *s { fmt.Println(i, "-", v) } } func main() { var s []int fmt.Println("slice by value") s = []int{0} f_slice_item(s) fmt.Println(s) s = []int{0} f_slice_size(s) fmt.Println(s) fmt.Println() fmt.Println("slice by pointer") s = []int{0} f_slice_pointer_item(&s) fmt.Println(s) s = []int{0} f_slice_pointer_size(&s) fmt.Println(s) fmt.Println() fmt.Println("iter") f_iter(&s) } Результат выполнения: slice by value [1] [0] slice by pointer [3] [0 5] iter 0 - 0 1 - 5

пятница, 14 февраля 2020 г.

в массиве содержатся разные элементы

#c #массивы #указатели


Вот код:

int *p;

// так как в функцию указатель в таком виде передать нельзя: компилятор заругается, то:

p = (int*)malloc(sizeof(int)); // в функции я переопределю его размер через realloc

func(p);

for (i = 0; i < count; i++)
    printf("%d ", p[i]); // возвращает мусор
/* если из функции делать вывод, то в консоль выводятся корректные значения */ 


Помогите понять, в чём тут дело ?

Прототип функции func:

void func(int *arr);


Делать int * не хочу пока что: хочу понять, что тут не так...
    


Ответы

Ответ 1



Итак, у вас есть указатель int* p; и вы хотите инициализировать его через вызов функции. Можно сделать так: void func(int** inPtr){ int* tmp = (int*)malloc(0x100); *inPtr = tmp; //(*inPtr)[2] = 0x02;//пишем 3-й элемент } void main(void){ int* p; func(&p); //printf("0x%X\n", p[2]);//читаем 3-й элемент } func принимает указатель на указатель на int, то есть адрес указателя на int. В main мы через ссылку получили адрес нашего указателя и передали его в функцию. В функции мы через разыменование из адреса получили сам указатель и записали в него выделенный буфер. Пляски с tmp нужны, чтобы в отладчике можно было посмотреть реальный адрес выделенного буфера и сравнить его с тем, что будет в p после выполнения funс()

Ответ 2



Я не очень понял, почему нельзя передать указатель в функцию и что за мусор получается, по этому попробую просто ответить примером: #include #include #define COUNT 15 void func(int *arg) { if((arg = (int*)malloc(sizeof(int) * COUNT)) == NULL) printf("allocation error\n"); } int main() { int *p, i; // почему в таком виде нельзя передать? func(p); // если в func() произошел allocation error, будет ошибка, // т.к. мы ничего не узнаем об этом // меняем мусор на что-то осмысленное for (i = 0; i < COUNT; i++) *(p + i) = i; // выводим массив for (i = 0; i < COUNT; i++) printf("%d ", p[i]); printf("\n"); return 0; }

Возврат из функции и её вызов в С++

#cpp #массивы #функции #указатели


Имею 2 функции (различий меж ними нет, практически)
Правильно ли я выполняю из них возврат? И как правильно их нужно вызывать в main
(между массивами, возвращаемых обеими функциями, нужно будет провести математические
операции)? 

int summa(int x[], int y[], int nRow)
{
        int *sum = new int[nRow];
        for (int i=0; i


Ответы

Ответ 1



Если вы хотите вернуть в первом случае sum[0], а во втором - dob[0], то правильно. После выполнения return функция прекращает работу - так что из всего цикла return будет выполнено только на первой итерации. Но если вы хотите вернуть выделенную для нового массива память - то объявляйте функцию как возвращающую int*, и просто возвращайте return sum. Потом не забудьте освободить выделенную память. int * summa(int x[], int y[], int nRow) { int *sum = new int[nRow]; for (int i=0; i

Ответ 2



Так как вам уже ответили, я лишь добавлю, что проще и лучше работать в таких случаях с std::valarray. Он позволит обойтись без функции и лишных забот. int x[] = { 1, 2, 3}; int y[] = { 4, 5, 6}; std::valarray vx(x, 3), vy(y, 3), result = vx + vy; for (int i : result) cout << i << endl;

Обмен через указатели

#c #указатели


Можно ли как то реализовать функцию swap которая не будет привязана к определенному типу?
    


Ответы

Ответ 1



Вроде тут ничего хитрого: void Swap(void * const p_left, void * const p_right, size_t const bytes_count) { size_t byte_index; for(byte_index = 0; bytes_count != byte_index; ++byte_index) { uint8_t * const p_left_byte = ((uint8_t *) p_left) + byte_index; uint8_t * const p_right_byte = ((uint8_t *) p_right) + byte_index; uint8_t const tmp = *p_left_byte; *p_left_byte = *p_right_byte; *p_right_byte = tmp; } }

Ответ 2



Ну в c99 можно еще проще! void swap(void *a, void *b, size_t size) { char temp[size]; // std=c99 memcpy(temp, b, size); memcpy(b, a, size); memcpy(a, temp, size); }

Ответ 3



вот собственно рабочий пример. В комментариях я допустил ошибку, жаль не поправить, void *c это одинарная ссылка. приведу тут правильный код функции swap: #define __swap_wrapper(A,B) __swap((void**)&A, (void**)&B) void __swap(void **a, void **b) { void *c = *a; *a = *b; *b = c; } __swap_wrapper - враппер для удобства описания, чтоб код не захламлять. можно также использовать intrinsics (gcc) с memory barrier, итд Измененный вариант Измененный вариант, с макросами определения типа и копированием данных, макросы базовых типов, все приводить не стал, можно добавлять по собственному вкусу, заточено под сборку gcc. Макрос и функции: #define __swap_wrapper(A,B) __extension__ \ (__builtin_choose_expr( \ __builtin_types_compatible_p(__typeof__(A), int[]), __swap(A, B, sizeof(A), sizeof(B)), \ __builtin_choose_expr( \ __builtin_types_compatible_p(__typeof__(A), const int[]), __swap(A, B, sizeof(A), sizeof(B)), \ __builtin_choose_expr( \ __builtin_types_compatible_p(__typeof__(A), unsigned int[]), __swap(A, B, sizeof(A), sizeof(B)), \ __builtin_choose_expr( \ __builtin_types_compatible_p(__typeof__(A), const unsigned int[]), __swap(A, B, sizeof(A), sizeof(B)), \ __builtin_choose_expr( \ __builtin_types_compatible_p(__typeof__(A), short[]), __swap(A, B, sizeof(A), sizeof(B)), \ __builtin_choose_expr( \ __builtin_types_compatible_p(__typeof__(A), const short[]), __swap(A, B, sizeof(A), sizeof(B)), \ __builtin_choose_expr( \ __builtin_types_compatible_p(__typeof__(A), unsigned short[]), __swap(A, B, sizeof(A), sizeof(B)), \ __builtin_choose_expr( \ __builtin_types_compatible_p(__typeof__(A), const unsigned short[]), __swap(A, B, sizeof(A), sizeof(B)), \ __builtin_choose_expr( \ __builtin_types_compatible_p(__typeof__(A), long[]), __swap(A, B, sizeof(A), sizeof(B)), \ __builtin_choose_expr( \ __builtin_types_compatible_p(__typeof__(A), const long[]), __swap(A, B, sizeof(A), sizeof(B)), \ __builtin_choose_expr( \ __builtin_types_compatible_p(__typeof__(A), unsigned long[]), __swap(A, B, sizeof(A), sizeof(B)), \ __builtin_choose_expr( \ __builtin_types_compatible_p(__typeof__(A), const unsigned long[]), __swap(A, B, sizeof(A), sizeof(B)), \ __builtin_choose_expr( \ __builtin_types_compatible_p(__typeof__(A), long long[]), __swap(A, B, sizeof(A), sizeof(B)), \ __builtin_choose_expr( \ __builtin_types_compatible_p(__typeof__(A), const long long[]), __swap(A, B, sizeof(A), sizeof(B)), \ __builtin_choose_expr( \ __builtin_types_compatible_p(__typeof__(A), unsigned long long[]), __swap(A, B, sizeof(A), sizeof(B)), \ __builtin_choose_expr( \ __builtin_types_compatible_p(__typeof__(A), unsigned const long long[]), __swap(A, B, sizeof(A), sizeof(B)), \ __builtin_choose_expr( \ __builtin_types_compatible_p(__typeof__(A), char*), __swap_p((void**)&A, (void**)&B), \ __builtin_choose_expr( \ __builtin_types_compatible_p(__typeof__(A), const char*), __swap_p((void**)&A, (void**)&B), \ __builtin_choose_expr( \ __builtin_types_compatible_p(__typeof__(A), char[]), __swap(A, B, (sizeof(A) - 1), (sizeof(B) - 1)), \ __builtin_choose_expr( \ __builtin_types_compatible_p(__typeof__(A), const char[]), __swap(A, B, (sizeof(A) - 1), (sizeof(B) - 1)), \ __builtin_choose_expr( \ __builtin_types_compatible_p(__typeof__(A), unsigned char*), __swap_p((void**)&A, (void**)&B), \ __builtin_choose_expr( \ __builtin_types_compatible_p(__typeof__(A), const unsigned char*), __swap_p((void**)&A, (void**)&B), \ __builtin_choose_expr( \ __builtin_types_compatible_p(__typeof__(A), unsigned char[]), __swap(A, B, (sizeof(A) - 1), (sizeof(B) - 1)), \ __builtin_choose_expr( \ __builtin_types_compatible_p(__typeof__(A), const unsigned char[]), __swap(A, B, (sizeof(A) - 1), (sizeof(B) - 1)), \ __builtin_choose_expr( \ __builtin_types_compatible_p(__typeof__(A), void*), __swap_p((void**)&A, (void**)&B), \ __builtin_choose_expr( \ __builtin_types_compatible_p(__typeof__(A), const void*), __swap_p((void**)&A, (void**)&B), \ __swap(A, B, sizeof(*A), sizeof(*B))))))))))))))))))))))))))))) void __swap_p(void **a, void **b) { void *c = *a; *a = *b; *b = c; } void __swap(void *a, void *b, size_t na, size_t nb) { if ((!na) || (na != nb)) { return; } char *va = (char*)a, *vb = (char*)b; if (va == vb) { return; } __builtin_prefetch(va + na, 1, 1); __builtin_prefetch(vb + nb, 1, 1); while (na--) { va[na] ^= vb[na]; vb[na] ^= va[na]; va[na] ^= vb[na]; } } ну и собственно рабочий пример (изменен). P.S. на досуге глянул и объединил обе функции, так же расширил типы на массивы. Как мне кажется,вполне юзабельно.

Ответ 4



Как сделал я: void swap_s(char *a, char *b, size_t n) { char tmp; for (int i=0; i

Освобождение памяти void* var

#cpp #указатели


Задача - сделать класс, имеющий переменную, тип которой может выбираться пользователем
самостоятельно. Как в деструкторе освободить выделенную память?

class UC{
private:
    void *var;
    ...
public:
    template
    UC(const T& tmp){
        var = new T;
        (*(T*)var) = tmp;
    }
    ~UC();                // ?
    ...
};

    


Ответы

Ответ 1



Это сильно напоминает std::any. Вам нужно в каждом объекте хранить указатель на функцию, которая будет удалять ваш указатель, предварительно скастовав его к T *. А выбирать правильную функцию нужно в шаблонном конструкторе. Вот так: class UC { private: void *var; void (*del_func)(void*); public: template UC(const T& tmp) { var = new T(tmp); del_func = [](void *p){delete static_cast(p);}; } ~UC() { del_func(var); } }; Я заменил var = new T; (*(T*)var) = tmp; на var = new T(tmp);. Так проще. Кроме того, очень советую почитать про the rule of three.

Ответ 2



Наверно я показываю плохой способ, но возможно обойтись вообще без явного деструктора :) Если использовать класс shared_ptr из STL: class UC { private: shared_ptr var; public: template UC(const T& tmp) : var { new T { tmp }, [](T * v) { delete v; } } { } void * getVar() { return var.get(); } }; int main() { string s { "test" }; UC test { s }; void * z = test.getVar(); cout << *((string *)(z)) << endl; } P.S.: вообще-то shared_ptr предназначен не для этого, но уж очень хорошо его функционал подходит :)

unique_ptr вызывает деструктор только при reset

#cpp #указатели #cpp17


Почему std::unique_ptr<...> вызывает деструктор целевого объекта только если вызвать 

std::unique_ptr<>::reset(nullptr);


При этом, если вызвать std::unique_ptr<>::release, то ничего не происходит, но указатель
обнуляется.

Что делать необходимо дабы не происходило таких ситуаций?
    


Ответы

Ответ 1



При вызове std::unique_ptr::release владение указателем передается вызывающему коду. Игнорирование возвращаемого значения является ошибкой. Что делать? Ничего. Не надо вызывать release если нет намерения вручную удалить объект.

четверг, 13 февраля 2020 г.

Обращение к переменной по ссылке в python

#python #python_3x #указатели #ссылка


Сегодня буду краток. Есть код:

a, b, c = 1, 2, 3
arr = [a, b, c]
for i in range(3):
    arr[i] += 1

print(a, b, c)  # output: 1 2 3


А мне нужно output: 2 3 4. Как можно, изменяя элементы в списке, изменить элементы,
которые были добавлены изначально? Что-то вроде ссылки и указателя?

UPD: я значительно упростил код, поэтому дам некоторые уточнения: так называемые
переменные a, b и c являются атрибутами класса, и я сомневаюсь, что можно решить проблему
через globals()
    


Ответы

Ответ 1



Выражение arr[i] += 1 соответствует: arr[i] = arr[i] + 1 что переписывает ссылку, на новую (с новым значением), поэтому ваш код и не работает. Вообще a, b, c = 1, 2, 3 означает, что переменные a, b, c хранят ссылки на объекты 1, 2, 3. А тут arr = [a, b, c] вы не ссылки на переменные положили, а сами ссылки на объекты 1, 2, 3. Вам проще к самим переменным обратиться и поменять значением, чем так. Либо оставить значения в словаре или в списке и работать с ними. Но если очень хочется, можно рефлексией поменять у переменных: a, b, c = 1, 2, 3 arr = [a, b, c] for i in ['a', 'b', 'c']: globals()[i] += 1 print(a, b, c) # 2 3 4 и я сомневаюсь, что можно решить проблему через globals() Через globals() и вправду не получится, т.к. оно меняет глобальные переменные, а вот для изменения в объектах использовать getattr и setattr: class Foo: a, b, c = 1, 2, 3 foo = Foo() arr = [foo.a, foo.b, foo.c] for i in ['a', 'b', 'c']: x = getattr(foo, i) setattr(foo, i, x + 1) print(foo.a, foo.b, foo.c) # 2 3 4

Ответ 2



Kогда мне нужно обеспечить изменение объекта "по ссылке", я применяю такой способ: a, b, c = [1], [2], [3] arr = [a, b, c] for i in range(3): arr[i][0] += 1 print(a[0], b[0], c[0]) # output: 2 3 4 Объяснение. Немного общей теории: Объекты числового типа неизменяемы, к такому объекту нельзя прибавить единицу, можно только создать новый числовой объект с нужным значением и заменить на него исходный. Соответственно: а = 1 означает: создай объект со значением 1 и помести его в глобальный словарь с переменными (globals) под именем a. а = a + 1 означает: найди в словаре с переменными объект под именем a, сложи его с объектом 1, результат запиши в новый объект 2. В глобальном словаре замени значение a ссылкой на этот объект. a, b, c = 1, 2, 3 - создай три числовых объекта, запиши их ссылки в словарь с переменными под именами a, b, c, соответственно. Тут ещё задействованы кортежи, но про них не буду говорить, чтобы не усложнять. Объекты типа list изменяемы, в список можно добавлять и удалять значения, можно менять уже находящиеся в списке. Список хранит не сами объекты, а ссылки на них, меняя значение в списке, мы на самом деле заменяем ссылку предыдущего объекта на новую, указывающую на другой объект. arr = [a, b, c] - создай объект типа список и наполни его ссылками на объекты, которые хранятся в словаре с переменными под именами a, b, c. Какая ссылка хранится под именем a? Ссылка на числовой объект 1. Под именем b ссылка на 2 и т. д. Таким образом, имеем arr = [1, 2, 3] - список arr не хранит ни самих переменных, ни информации из каких переменных объекты 1, 2, 3 были получены. Кстати, теперь каждый из трёх объектов 1, 2, 3 имеет на себя как минимум по две ссылки - одна в глобальном словаре с переменными (a, b, c), другая внутри списка arr. Когда вы делаете arr[0] += 1, вы берёте из списка нулевой элемент, который представляет из себя ссылку на числовой объект 1 и прибавляете к этому объекту единицу. Информации про переменную, с помощью которой вы этот объект назначали, в списке нет. Результат (ссылку на новый объект) записываете обратно в нулевой элемент списка. Использование списка для сохраненения ссылки на значение переменной: a, b, c = [1], [2], [3] - оборачиваем каждый числовой объект в список и пишем ссылку на этот список в переменную. Помним, что объект типа список хранит не сам числовой объект, а ссылку на него. Следовательно, под именем a у нас хранится ссылка на список, который содержит единственный элемент - ссылку на объект 1. arr = [a, b, c] - эту строчку можно представить как arr = [[1], [2], [3]]. Информацию о переменных a, b, c мы потеряли также, как и в предыдущем случае, однако сейчас и переменная a, и arr[0] указывают на список [1]. И этот список содержит нужное нам значение, которое мы можем менять, сохраняя в a и arr[0] ссылку на список-обёртку. arr[0][0] += 1 - возьми нулевой элемент из списка. В нашем случае это [1]. Возьми и у него нулевой элемент. Это будет числовой объект 1. Прибавь к нему единицу. Получившимся новым объектом замени нулевой элемент в списке [1]. Список мы не трогаем, а лишь меняем внутри него элемент на другой. Ссылка на список остаётся неизменной, переменная a продолжает на него указывать, только теперь этот список содержит 2 вместо 1. Мой ответ на похожую тему. В мануале на английском этот способ под № 3: How do I write a function with output parameters (call by reference)?.

Ответ 3



a, b, c = 1, 2, 3 arr = [a, b, c] a, b, c = map(lambda x: x+1, arr) print(a, b, c) 2 3 4

Как работает этот указатель на функцию?

#указатели #cpp #функции


void error(int i);
void (*p)(int);
p=&error;
(*p)(1);

Для начала попробую прочитать вот вторую строчку, то есть p - это указатель на функцию
возвращающую значения типа void. Почему я прочитал так и в частности "типа void", потому
что использовал методику отсюда. Но а по каким правилам это все происходит?
Я примерно понимаю это как : 


реализация функции(просто для примера, а так объявление тоже было, но его тут не
написал); 

уже читал его выше, дополню только то, что группирующие скобки нужны для того, чтобы
был указатель на функцию, а не имя функции и её возвращающий тип void*. Странно что
после прочтения одной книги по C++, я не встретил там упоминания о основном типе и
производном и по этому меня такая запись удивила, что уж говорить о такой int (*(*foo)())();,
это было бы вообще темным лесом, если бы не та методика.

Тут указателю "p" присваивается адрес, но адрес на что, на прототип функции?

Указатель разыменовывается и просто на его место подставляется имя функции error
и получается (error)(1)? То есть можно любую функцию оборачивать группирующими скобками
и от этого суть не меняется.  
 Такая запись нужна только потому что этого требует указатели, то есть чтобы указатель
имел тот же тип что и указываемый объект? 
    


Ответы

Ответ 1



Хороший вопрос. Вообще тема описания указателей на функции пожалуй самая запутанная в Си и особенно в С++. Наибольшие проблемы возникают с корректным для компилятора описанием функции, возвращающей адрес функции. Практически надо понимать, что указатель на функцию это адрес первой машинной команды тела функции и ее вызов производится путем загрузки этого адреса в регистр и выдачи инструкции call для регистра, например movq -16(%rbp), %rax // загрузка адреса функции call *%rax // вызов На практике удобно (согласен, что не совсем правильно, но реально работает) описывать адреса функций как void * и при необходимости использовать приведение типа. Например: (работает и в Си и в С++) // fu1.c вызов функций по адресу #include // если первый аргумент не 0, то // возвращает второй аргумент (передаваемую функцию), // иначе определенную в fu2.c функцию extern void *getfu(int, int (*f)(int)); // возвращает массив определенных в fu2.c функций extern void **getvfu(); static int qq (int i) // передаем эту функцию как аргумент в getfu() { return printf("qq=%d\n",i); } int main (int ac, char *av[]) { int i, (*p)(int); void **v = getvfu(); // получим массив функций из fu2.c for (i = 0; v[i]; i++) ((int (*)(int))(v[i]))(i); p = (int (*)(int))(av[1] ? getfu(0,0) : getfu(1,qq)); int rc = p(10); return printf ("rc = %d\n",rc) < 0; } // fu2.c функции и их массив #include static int fu (int i) { return printf ("fu=%d\n",i); } void * getfu (int fa, int (*f)(int)) { return (void *)(fa ? f : fu); } static int f1 (int i) { return printf ("f1: %d\n",i); } static int f2 (int i) { return printf ("f1: %d\n",i); } static int f3 (int i) { return printf ("f1: %d\n",i); } void ** getvfu() { static void *fv[] = {(void *)f1, (void *)f2, (void *)f3, (void *)fu, 0}; return fv; } Транслируем и запускаем avp@avp-ubu1:~/hashcode$ g++ -c fu1.c fu2.c avp@avp-ubu1:~/hashcode$ g++ fu[12].o -o a++ avp@avp-ubu1:~/hashcode$ ./a++ 1 f1: 0 f1: 1 f1: 2 fu=3 fu=10 rc = 6 avp@avp-ubu1:~/hashcode$ gcc fu1.c fu2.c avp@avp-ubu1:~/hashcode$ ./a.out 1 f1: 0 f1: 1 f1: 2 fu=3 fu=10 rc = 6 avp@avp-ubu1:~/hashcode$ ./a.out f1: 0 f1: 1 f1: 2 fu=3 qq=10 rc = 6 avp@avp-ubu1:~/hashcode$ Возможно это (не совсем корректное решение) окажется полезным. Кстати, кто-нибудь может напишет действительно правильный (желательно собирающийся и работающий) вариант для С и С++.

Ответ 2



@avp Правильно -- это так? static int f1 (int i) { return printf ("f1: %d\n",i); } static int f2 (int i) { return printf ("f1: %d\n",i); } static int f3 (int i) { return printf ("f1: %d\n",i); } static int fu (int i) { return printf ("fu=%d\n",i); } int (** getvfu())(int) { static int (*fv[])(int) = {f1, f2, f3, fu, 0}; return fv; } int main(int argc, char* argv[]) { int (**fv)(int) = getvfu(); fv[0](1); fv[1](2); fv[2](3); fv[3](4); return 0; } Случайно наткнулся ещё вот на что: Reading C type declarations @mzarb там узнал что переменные не передаются, а присваиваются в месте между телом функции и сигнатурой, Не совсем понятно, что Вы написали. Традиционно (допускаю, что не всегда и везде) параметры в функцию в C передаются через стек. Есть разные методы передачи параметров: "по значению" (передаётся значение, функция с ним может делать, что хочет, в вызывающей программе/функции значение параметра не меняется). В C все параметры именно так и передаются. Есть метод передачи "по ссылке" -- в функцию/подпрограмму передаётся ссылка на переменную (или значение, и там может получиться смешно, если функция будет это значение менять. Одна из типичных ошибок). В функции идёт работа с переменной-фактическим параметром через эту ссылку. Классика жанра -- фортран. В Си такого нет, но есть возможность смоделировать такую работу, передав в качестве параметра указатель на переменную. Сам указатель передаётся "по значению", а с переменной функция может делать всё, что захочет. Есть другие методы, например в алгол-60 был метод передачи параметров "по имени". В большинстве случаев выполнял те же задачи, что и метод передачи параметров "по ссылке", но имел некоторые дополнительные возможности (сейчас в некоторых языках есть подобные вещи -- параметры-блоки кода, вроде Obj-c, perl и пр.). Реализация -- thunk'и. Сигнатура тут ни причём. Она служит только для проверки правильности и добавки некоторых приведений типа по умолчанию.

воскресенье, 9 февраля 2020 г.

Длина адреса переменной (указателя)

#cpp #указатели


short int a;
short int *pa;
pa = &a;
*pa = 10;
cout << "a  = " << a << endl; //a  = 10
cout.setf(ios::hex);
cout << "&a = " << &a;        //&a = 0x7ffc079b67ae


Неужели short int хватает чтобы записать такой длинный адрес переменной? Насколько
мне известно, адреса должны выглядеть примерно так 0x7ffc079b67ae. Неужели short int
хватает чтобы записать такой адрес, или запись адреса в указатель происходит каким-то
другим образом? Объясните, пожалуйста, кто разбирается. Или это происходит как-то по
какой-то ссылке на указатель?
    


Ответы

Ответ 1



Проведем аналогию - адрес на конверте в общем-то, всегда одного размера, указываете ли вы на небоскреб или на собачью будку :) Т.е. размер указателя не связан с размером того, на что он указывает. А чтобы понять, какого конкретно размера ваш указатель, добавьте в программу следующие строки: cout << "Размер a = " << sizeof(a) << " байт\n"; cout << "Размер pa = " << sizeof(pa) << " байт\n"; и посмотрите, что они выведут.