Указатели и функции указателя - PullRequest
9 голосов
/ 22 октября 2011

При изучении книги K & R в CI было несколько вопросов, касающихся сложных объявлений указателей и связей между массивами указателей.

1) В чем именно разница между

char amessage[] = "this is a string";

и

char *pmessage
pmessage = "this is a string"

а когда бы вы использовали один или другой?

Насколько я понимаю, первый выделяет некоторый объем памяти в соответствии с размером строки, а затем сохраняет символы в памяти.Затем, когда вы получаете доступ к amessage [], вы просто получаете прямой доступ к тому символу, который ищете.Для второго вы также выделяете память, за исключением того, что вы просто получаете доступ к данным через указатель, когда вам это нужно.Это правильный взгляд на это?

2) В книге говорится, что массивы при передаче в функции обрабатываются так, как если бы вы указали указатель на первый индекс массива, и, таким образом, вы манипулируете массивом, манипулируя указателем, даже если вы все еще можете использовать синтаксис, подобныйа [I].Верно ли это, если вы только что создали массив где-то и хотите получить к нему доступ, или это правда, только если вы передаете массив в функцию?Например:

char amessage[]= "hi";
char x = *(amessage + 1); // can I do this?

3) В книге сказано, что использование статики прекрасно в этой конкретной функции:

/* month_name:  return name of n-th month */
char *month_name(int n)
{
    static char *name[] = {
       "Illegal month",
       "January", "February", "March",
       "April", "May", "June",
       "July", "August", "September",
       "October", "November", "December"
   };
   return (n < 1 || n > 12) ? name[0] : name[n];
}

Я не понимаю, почему именно это хорошее применениестатичный.Это потому, что char * name [] будет удалено после возврата функции, если оно не статично (потому что это локальная переменная)?Значит ли это, что в c вы не можете делать такие вещи, как:

void testFunction(){
    int x = 1;
    return x; 
}

Без удаления x перед использованием возвращаемого значения?(Извините, я думаю, это не вопрос указателя, но это было в главе, посвященной указателям).

4) Есть несколько сложных объявлений, таких как

char (*(*x())[])()

Я действительно запутался в том, чточто здесь происходит.Таким образом, часть x () означает функцию x, которая возвращает указатель?Но какого рода указатель возвращает просто «» без int, void или w / e.Или это означает указатель на функцию (но я подумал, что это будет похоже на (* x) ())?А затем после того, как вы добавите скобки (потому что я предполагаю, что скобки имеют следующий приоритет) ... что это?Массив функций?

Этот вид связан с моей путаницей с указателями на функции.Если у вас есть что-то вроде

int (*func)() 

Это означает, что указатель на функцию, которая возвращает int, и имя этого указателя - func, но что это значит, когда его значение равно int (* x [3]) ().Я не понимаю, как вы можете заменить имя указателя на массив.

Спасибо за любую помощь!

Кевин

Ответы [ 4 ]

7 голосов
/ 22 октября 2011

1) В чем разница между

char amessage[] = "this is a string";

и

char *pmessage
pmessage = "this is a string"

и когда вы будете использовать один или другой?

amessage будет всегда относится к памяти, содержащей this is a string\0.Вы не можете изменить адрес, на который он ссылается.pmessage может быть обновлено, чтобы указывать на любой символ в памяти, независимо от того, является ли он частью строки.Если вы назначите pmessage, вы можете потерять единственную ссылку на this is a string\0.(Это зависит от того, делали ли вы ссылки где-либо еще.)

Я бы использовал char amessage[], если бы намеревался изменить содержимое amessage[] на месте.Вы не можете изменить память, на которую указывает pmessage.Попробуйте эту маленькую программу;закомментируйте amessage[0]='H' и pmessage[0]='H'; по одному и увидите, что pmessage[0]='H'; вызывает нарушение сегментации:

#include <stdio.h>

int main(int argc, char* argv[]) {
    char amessage[]="howdy";
    char *pmessage="hello";
    amessage[0]='H';
    pmessage[0]='H';
    printf("amessage %s\n", amessage);
    printf("pmessage %s\n", pmessage);
    return 0;
}

Изменение строки, жестко закодированной в программе, встречается относительно редко;char *foo = "literal";, вероятно, более распространено, и неизменность строки может быть одной из причин.

2) В книге говорится, что массивы при передаче в функции обрабатываются так, как если бы вы указали указатель напервый индекс массива и, таким образом, вы манипулируете массивом посредством манипулирования указателем, даже если вы все еще можете использовать синтаксис как [i].Верно ли это, если вы только что создали массив где-то и хотите получить к нему доступ, или это правда, только если вы передаете массив в функцию?Например:

char amessage[]= "hi";
char x = *(amessage + 1); // can I do this?

Вы можете сделать это, однако это довольно необычно:

$ cat refer.c
#include <stdio.h>

int main(int argc, char* argv[]) {
    char amessage[]="howdy";
    char x = *(amessage+1);
    printf("x: %c\n", x);
    return 0;
}

$ ./refer
x: o
$ 

По крайней мере, я никогда не видел "производственную" программу, которая делает этос символьными строками.(И мне трудно думать о программе, которая использует арифметику указателей, а не подписку на массивы для массивов других типов.)

3) Книга говорит, что использование static прекрасно в этомконкретная функция:

/* month_name:  return name of n-th month */
char *month_name(int n)
{
    static char *name[] = {
       "Illegal month",
       "January", "February", "March",
       "April", "May", "June",
       "July", "August", "September",
       "October", "November", "December"
   };
   return (n < 1 || n > 12) ? name[0] : name[n];
}

Я не понимаю, почему именно это хорошее использование static.Это потому, что char *name[] будет удалено после возврата функции, если это не static (потому что это локальная переменная)?Значит ли это, что в c вы не можете делать такие вещи, как:

void testFunction(){
    int x = 1;
    return x; 
}

Без удаления x перед использованием возвращаемого значения?(Извините, я думаю, это может быть не вопрос указателя, но это было в главе, посвященной указателям.)по крайней мере, GCC может определить, что строки не изменены, и сохранить их в сегменте данных .rodata только для чтения.Однако это может быть оптимизация со строковыми литералами.Ваш пример с другим примитивным типом данных (int) также работает нормально, потому что C передает все по значению как при вызовах функций, так и при возвратах функций.Однако, если вы возвращаете указатель на объект, размещенный в стеке, то static абсолютно необходим, поскольку он определяет, где в памяти находится объект:

$ cat stackarray.c ; make stackarray
#include <stdio.h>

struct foo { int x; };

struct foo *bar() {
    struct foo array[2];
    array[0].x=1;
    array[1].x=2;
    return &array[1];
}

int main(int argc, char* argv[]) {
    struct foo* fp;
    fp = bar();

    printf("foo.x: %d\n", fp->x);
    return 0;
}

cc     stackarray.c   -o stackarray
stackarray.c: In function ‘bar’:
stackarray.c:9:2: warning: function returns address of local variable

Если вы измените продолжительность храненияот array до static, то возвращаемый адрес не автоматически назначается и будет продолжать работать даже после возврата функции:

$ cat staticstackarray.c ; make staticstackarray ; ./staticstackarray
#include <stdio.h>

struct foo { int x; };

struct foo *bar() {
    static struct foo array[2];
    array[0].x=1;
    array[1].x=2;
    return &array[1];
}

int main(int argc, char* argv[]) {
    struct foo* fp;
    fp = bar();

    printf("foo.x: %d\n", fp->x);
    return 0;
}

cc     staticstackarray.c   -o staticstackarray
foo.x: 2

Вы можетеПосмотрите, где изменяется распределение памяти между stackarray и staticstackarray:

$ readelf -S stackarray | grep -A 3 '\.data'
  [24] .data             PROGBITS         0000000000601010  00001010
       0000000000000010  0000000000000000  WA       0     0     8
  [25] .bss              NOBITS           0000000000601020  00001020
       0000000000000010  0000000000000000  WA       0     0     8
$ readelf -S staticstackarray | grep -A 3 '\.data'
  [24] .data             PROGBITS         0000000000601010  00001010
       0000000000000010  0000000000000000  WA       0     0     8
  [25] .bss              NOBITS           0000000000601020  00001020
       0000000000000018  0000000000000000  WA       0     0     8

Раздел .bss в версии без static на 8 байт меньше, чем раздел .bss в версии сstatic.Эти 8 байтов в разделе .bss обеспечивают возвращаемый постоянный адрес.

Таким образом, вы можете видеть, что случай со строками на самом деле не имеет значения - по крайней мере, GCC это не волнует -но указатели на другие типы объектов, static имеют все значение в мире.

Однако большинство функций, которые возвращают данные в хранилище function-local- static, вышли из строя.strtok(3), например, извлекает токены из строки, и если последующие вызовы strtok(3) включают NULL в качестве первого аргумента, чтобы указать, что функция должна повторно использовать строку, переданную в first вызов.Это удобно, но означает, что программа никогда не может токенизировать две отдельные строки одновременно, а многопоточные программы не могут надежно использовать эту процедуру.Таким образом, доступна версия reentrant , strtok_r(3), которая принимает дополнительный аргумент для хранения информации между вызовами.man -k _r покажет удивительное количество функций, в которых доступны реентерабельные версии, и основным изменением является сокращение использования static в функциях.

4) Есть несколько сложных объявлений, таких как

char (*(*x())[])()

Я действительно смущен тем, что происходит.То есть часть x() означает функцию x, которая возвращает указатель?Но какого рода указатель возвращает просто «» без int, void или w / e.Или это означает, что указатель на функцию (но я подумал, что это будет похоже на (*x)())? А потом после добавления скобок (потому что я предполагаю, что скобки имеют следующий приоритет) ... что это? Массив функций?

Этот вид связан с моей путаницей с указателями на функции. Если у вас есть что-то вроде

int (*func)() 

Это означает, что указатель на функцию, которая возвращает int, и имя этого указателя является func, но что это значит, когда это похоже на int (*x[3])(). Я не понимаю, как вы можете заменить имя указателя на массив.

Во-первых, не паникуйте. Вы будетепочти никогда ничего не нужно , это сложно. Иногда очень удобно иметь таблицу указателей функций и вызывать следующий на основе диаграммы перехода состояний. Иногда вы устанавливаете обработчики сигналов с sigaction(2).Тогда вам понадобятся немного более сложные указатели функций, однако, если вы используете cdecl(1) для расшифровки того, что вам нужно, это будет иметь смысл:

       struct sigaction {
           void     (*sa_handler)(int);
           void     (*sa_sigaction)(int, siginfo_t *, void *);
           sigset_t   sa_mask;
           int        sa_flags;
           void     (*sa_restorer)(void);
       };

cdecl(1) понимает только подмножество of C нативных типов, поэтому замените siginfo_t на void, и вы можете примерно увидеть, что требуется:

$ cdecl
Type `help' or `?' for help
cdecl> explain void     (*sa_sigaction)(int, void *, void *);
declare sa_sigaction as pointer to function
    (int, pointer to void, pointer to void) returning void

Программирование на Expert C: Deep C Secrets имеет отличную главу, посвященнуюдля понимания более сложных объявлений и даже включает версию cdecl, на случай, если вы захотите расширить ее, чтобы включить больше типов и обработку typedef.Это стоит прочитать.

2 голосов
/ 22 октября 2011

Это связано с частью 3 и является своего рода ответом / дополнением к комментарию Сарнольда.Он прав в том, что со статическим или без него строковые литералы всегда будут отделены от сегмента .data .rodata и, по сути, будут созданы только один раз.Однако без использования слова static фактический массив, то есть массив указателей на символы , будет фактически создаваться в стеке каждый раз, когда вызывается функция.

С использование статического:

Dump of assembler code for function month_name:
   0x08048394 <+0>:   push   ebp
   0x08048395 <+1>:   mov    ebp,esp
   0x08048397 <+3>:   cmp    DWORD PTR [ebp+0x8],0x0
   0x0804839b <+7>:   jle    0x80483a3 <month_name+15>
   0x0804839d <+9>:   cmp    DWORD PTR [ebp+0x8],0xc
   0x080483a1 <+13>:  jle    0x80483aa <month_name+22>
   0x080483a3 <+15>:  mov    eax,ds:0x8049720
   0x080483a8 <+20>:  jmp    0x80483b4 <month_name+32>
   0x080483aa <+22>:  mov    eax,DWORD PTR [ebp+0x8]
   0x080483ad <+25>:  mov    eax,DWORD PTR [eax*4+0x8049720]
   0x080483b4 <+32>:  pop    ebp
   0x080483b5 <+33>:  ret 

Без Использование статического:

Dump of assembler code for function month_name:
   0x08048394 <+0>:   push   ebp
   0x08048395 <+1>:   mov    ebp,esp
   0x08048397 <+3>:   sub    esp,0x40
   0x0804839a <+6>:   mov    DWORD PTR [ebp-0x34],0x8048514
   0x080483a1 <+13>:  mov    DWORD PTR [ebp-0x30],0x8048522
   0x080483a8 <+20>:  mov    DWORD PTR [ebp-0x2c],0x804852a
   0x080483af <+27>:  mov    DWORD PTR [ebp-0x28],0x8048533
   0x080483b6 <+34>:  mov    DWORD PTR [ebp-0x24],0x8048539
   0x080483bd <+41>:  mov    DWORD PTR [ebp-0x20],0x804853f
   0x080483c4 <+48>:  mov    DWORD PTR [ebp-0x1c],0x8048543
   0x080483cb <+55>:  mov    DWORD PTR [ebp-0x18],0x8048548
   0x080483d2 <+62>:  mov    DWORD PTR [ebp-0x14],0x804854d
   0x080483d9 <+69>:  mov    DWORD PTR [ebp-0x10],0x8048554
   0x080483e0 <+76>:  mov    DWORD PTR [ebp-0xc],0x804855e
   0x080483e7 <+83>:  mov    DWORD PTR [ebp-0x8],0x8048566
   0x080483ee <+90>:  mov    DWORD PTR [ebp-0x4],0x804856f
   0x080483f5 <+97>:  cmp    DWORD PTR [ebp+0x8],0x0
   0x080483f9 <+101>: jle    0x8048401 <month_name+109>
   0x080483fb <+103>: cmp    DWORD PTR [ebp+0x8],0xc
   0x080483ff <+107>: jle    0x8048406 <month_name+114>
   0x08048401 <+109>: mov    eax,DWORD PTR [ebp-0x34]
   0x08048404 <+112>: jmp    0x804840d <month_name+121>
   0x08048406 <+114>: mov    eax,DWORD PTR [ebp+0x8]
   0x08048409 <+117>: mov    eax,DWORD PTR [ebp+eax*4-0x34]
   0x0804840d <+121>: leave  
   0x0804840e <+122>: ret 

Как вы можете видеть во втором примере ( без статического ), массив выделяется в стеке каждый раз:

0x08048397 <+3>:   sub    esp,0x40

и указатели загружаются в массив:

0x0804839a <+6>:   mov    DWORD PTR [ebp-0x34],0x8048514
0x080483a1 <+13>:  mov    DWORD PTR [ebp-0x30],0x8048522
...

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

1 голос
/ 22 октября 2011

3) Это не имеет никакого отношения к этому - static создает массив один раз, а не создает его каждый раз при запуске функции.Поскольку данные в массиве никогда не меняются, более эффективно не создавать их заново каждый раз.Ваш пример функции будет работать нормально, каждый раз.Это ценность.Он не будет удален до того, как вы сможете его вернуть.Это было бы очень неинтуитивно.

0 голосов
/ 23 марта 2014

4) Добавление дополнительной информации в ответ на 4) пункт:
Я слежу за следующей книгой по изучению C: C для программистов на паскале Нормана Дж. Лэндиса.
Она довольно старая и считается мостом от паскаля к C; но я нахожу это настолько полезным, завершенным и объясненным на самом низком уровне машины. Для меня это потрясающая книга.
Глава 5.3.1 в приложении A говорит именно об этом. (Цитаты - это контент, извлеченный из книги)
Определение базового типа:

Спецификатор типа, появляющийся в объявлении, содержащем декларатор, называется> базовым типом

По сути, в bool x => bool - базовый тип, а в int x[] => базовый тип для массива - int, а базовый тип для x - массив int.

Для интерпретации сложных деклараторов применяются следующие правила:

  1. Сначала примените оператор звездочки.
  2. Применять операторы «функция базового типа» (()) и «массив возвращаемого базового типа» ([])> впоследствии справа налево. Конечно, скобки могут содержать декларатор, чтобы изменить порядок оценки.

И там тот же самый пример, заменяющий букву x буквой w:

Как я "разбираю" это: char (* (* w ()) []) ();

Я иду из-за скобок внутрь, после того, как я следую 2 правилам, упомянутым выше. Шаги:

  1. За пределами скобок мы находим декларатор функции. Тогда, пока у нас есть функция, возвращающая символ.
  2. Теперь мы вводим скобки и обрабатываем предшествующий указатель и массив.
  3. Такой указатель является указателем на «верхний базовый тип», который, скажем, является функцией возвращая чарса Затем мы получили указатель на функцию, возвращающую символ, пока.
  4. После массива это массив "верхнего базового типа". И «верхний базовый тип» = указатель на функцию, возвращающую символ.
  5. Теперь, перейдите в самые глубокие скобки, мы найдем указатель и функцию. Таким же образом, первый указатель, после функции.
  6. Мы обрабатываем указатель => указатель на массив указателей на функции, возвращающие символ.
  7. И, наконец, объявление функции, и мы получили: Функция, возвращающая указатель на массив указателей на функции, возвращающие символ.

Надеюсь, теперь все ясно.

Но вам понадобится время и практика, чтобы по-настоящему понять и передать это, но как только вы это получите, это довольно легко;)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...