Вопрос про таблицу строк и про функции scanf и printf

Тема в разделе "LANGS.C", создана пользователем Antolflash, 19 янв 2010.

  1. Antolflash

    Antolflash New Member

    Публикаций:
    0
    Регистрация:
    14 дек 2008
    Сообщения:
    167
    Я тут прочёл про таблицу строк в C++ и не поверил, глянул код, генерируемый студией при объявлении чего-то вроде char m[] = "String". Смотрю, через основные регистры в память по адресу m перекидывается строка из совершенно другого адреса. То бишь строка лежит в двух местах - в массиве и в месте, откуда её перекинули в массив - в таблице строк. Зачем так дублировать?
    И ещё вопрос. Каким образом можно создать функции с нефиксированным числом параметров? Каким образом работают printf и scanf, если у них в прототипе нет числа параметров?
     
  2. Antolflash

    Antolflash New Member

    Публикаций:
    0
    Регистрация:
    14 дек 2008
    Сообщения:
    167
    Код (Text):
    1. #include <stdio.h>
    2.  
    3. int my(const char *f, ...)
    4. {
    5.     printf("%s", f);
    6.     return 0;
    7. }
    8.  
    9. int main()
    10. {
    11.     double a = 5.123456789;
    12.     printf("%lf", a);
    13.     my("Hello!", a);
    14.     return 0;
    15. }
    Хм... работает. А доставать аргументы из стека можно только асмовскими вставками?
     
  3. n0name

    n0name New Member

    Публикаций:
    0
    Регистрация:
    5 июн 2004
    Сообщения:
    4.336
    Адрес:
    Russia
    va_start/va_arg/va_end
     
  4. Antolflash

    Antolflash New Member

    Публикаций:
    0
    Регистрация:
    14 дек 2008
    Сообщения:
    167
    http://msdn.microsoft.com/en-us/library/fxhdxye9(VS.80).aspx
    Только нашёл)
     
  5. Ustus

    Ustus New Member

    Публикаций:
    0
    Регистрация:
    8 авг 2005
    Сообщения:
    834
    Адрес:
    Харьков
    Antolflash
    работать-то работает, но имхо (для соответствия стандарту и вообще во избежание) лучше так:
    Код (Text):
    1. int my(const char* f, ...)
    2. {
    3.     va_list args;
    4.     va_start(args, f);
    5.     int r = vprintf(f, args);
    6.     va_end(args);
    7.     return r;
    8. }
     
  6. Ustus

    Ustus New Member

    Публикаций:
    0
    Регистрация:
    8 авг 2005
    Сообщения:
    834
    Адрес:
    Харьков
    А вообще в плюсах этого лучше избегать. Вот в стандарте C++0x это уже более цивилизованно сделают... может быть... лет через надцать... :) Хотя gcc уже многое поддерживает.
     
  7. gorodon

    gorodon New Member

    Публикаций:
    0
    Регистрация:
    19 окт 2009
    Сообщения:
    301
    >Я тут прочёл про таблицу строк в C++ и не поверил, глянул код, генерируемый студией при объявлении
    >чего-то вроде char m[] = "String". Смотрю, через основные регистры в память по адресу m перекидывается
    >строка из совершенно другого адреса. То бишь строка лежит в двух местах - в массиве и в месте, откуда её
    >перекинули в массив - в таблице строк. Зачем так дублировать?

    Вы объявляете
    char m[] = "String";

    - m - указатель на данные типа char
    - "String" - КОНСТАНТНАЯ переменная, хранится в сегменте данных

    если бы m указывал на КОНСТАНТНУЮ строку в сегменте данных, то вы бы получили access violation при изменении строки m(например, m[2]='i';) - а это стандарт позволяет, т.к. m - не КОНСТАНТА.

    У Рихтера, по-моему, тоже об этом написано было...
    Надо писать
    const char m[] = "String";
    тогда "дубляжа" не будет.
     
  8. J0E

    J0E New Member

    Публикаций:
    0
    Регистрация:
    28 июл 2008
    Сообщения:
    621
    Адрес:
    Panama
    char m[]; // объявления массива а не указателя
    char m[] = "String"; // объявления массива с инициализацией которая и выполняется каждый раз копированием
    static const char m[] = "String"; // объявления массива с инициализацией которая выполняется один раз (возможно при компиляции, но не факт)
    const*const m="String"; // объявление указателя на сроку, сам указатель занимает память но в силу константности компилятор может его не создавать.

    Что такое таблица строк в С++?
     
  9. cupuyc

    cupuyc New Member

    Публикаций:
    0
    Регистрация:
    2 апр 2009
    Сообщения:
    763
    Antolflash ты в каждом разделе форума этот вопрос задаёшь? тебе в begginers на него уже ответили.
     
  10. J0E

    J0E New Member

    Публикаций:
    0
    Регистрация:
    28 июл 2008
    Сообщения:
    621
    Адрес:
    Panama
    В беггинерс ты ответил не на вопрос "Зачем так дублировать?", а на "как избежать дублирования".
     
  11. gorodon

    gorodon New Member

    Публикаций:
    0
    Регистрация:
    19 окт 2009
    Сообщения:
    301
    char m[]; // объявления массива а не указателя
    J0E, приведите различия между объявлениями

    char m[];
    и
    char *m;

    (Уж то, что во втором случае m-указатель, вы согласитесь?)
     
  12. cppasm

    cppasm New Member

    Публикаций:
    0
    Регистрация:
    18 июл 2006
    Сообщения:
    923
    Вот тебе различия:
    char m[]="1234";
    Выделяется память под массив 5 байт (четыре символа + ноль).
    Элементы массива можно модифицировать.
    char *m="1234";
    Выделяется память под массив 5 байт (четыре символа + ноль) плюс 4 байта под сам указатель (зависит от разрядности адреса).
    Создаётся константный символьный литерал "1234" который менять нельзя, и его адрес пишется в переменную m.
    Разница как минимум в том, что во втором варианте строку модифицировать нельзя.
    Плюс под указатель выделяется отдельная память.
     
  13. gorodon

    gorodon New Member

    Публикаций:
    0
    Регистрация:
    19 окт 2009
    Сообщения:
    301
    J0E,cppasm

    char m[]="1234";
    -выделяется память на стеке функции (5 байт), заполняется символами '1','2','3','4' и ноль
    Заполнение идет из сегмента данных.
    Т.е. J0E здесь прав.

    char *m="1234";
    -выделяется память на стеке функции под указатель (4 байта)
    Этому указателю присваивается значение, указывающее на символьный литерал, размещенный в сегменте данных(на данные страницы памяти возможно запись-чтение). Т.е. изменять его МОЖНО.

    Вот пример кода:
    Код (Text):
    1. #include <stdio.h>
    2.  
    3. void some_func()
    4. {
    5.     static int i = 0;
    6.     i++;
    7.     //
    8.     //char m[] = "one!!!";
    9.     char *m = "one!!!";
    10.     //
    11.     printf(m);
    12.     printf(" - ");
    13.     //
    14.     switch(i){
    15.     case 1:
    16.         strcpy(m,"one   ");
    17.         break;
    18.     case 2:
    19.         strcpy(m,"two   ");
    20.         break;
    21.     case 3:
    22.         strcpy(m,"three ");
    23.         break;
    24.     default:
    25.         strcpy(m,"nul");
    26.     }
    27.     //
    28.     printf(m);
    29.     printf("\n");
    30.     //
    31.     printf ("address: %08X\n",(DWORD)m);
    32. };
    33.  
    34. int main(int argc, char* argv[])
    35. {
    36.     some_func();
    37.     //
    38.     some_func();
    39.     //
    40.     some_func();
    41.     //
    42.     some_func();
    43.     //
    44.     return 0;
    45. }
    Результаты для "char m[]":
    one!!! - one
    address: 0012FF78
    one!!! - two
    address: 0012FF78
    one!!! - three
    address: 0012FF78
    one!!! - nul
    address: 0012FF78

    Результаты для "char *m":
    one!!! - one
    address: 00406064
    one - two
    address: 00406064
    two - three
    address: 00406064
    three - nul
    address: 00406064

    Таблица сегментов исполняемого файла:
    ИМЯ |Размер |Размер о |ОАП память|ОАП файл|Описание
    .text |0X00003C26|0X00004000|0X00001000|0X00001000|Code,R,exec
    .rdata |0X0000080E|0X00001000|0X00005000|0X00005000|iData,R
    .data |0X00001E48|0X00001000|0X00006000|0X00006000|iData,RW
     
  14. J0E

    J0E New Member

    Публикаций:
    0
    Регистрация:
    28 июл 2008
    Сообщения:
    621
    Адрес:
    Panama
    Код (Text):
    1. #include <iostream>
    2.  
    3. int main()
    4. {
    5.   char m1[] = "1234";
    6.   char* m2  = "1234";
    7.   std::cout << typeid(m1).name() << '\n';
    8.   std::cout << typeid(m2).name() << '\n';
    9. }
    вывод:
    char [5]
    char *
     
  15. J0E

    J0E New Member

    Публикаций:
    0
    Регистрация:
    28 июл 2008
    Сообщения:
    621
    Адрес:
    Panama
    char *m // указатель на не константу, поэтому модификация возможна.
    Пример #13 скомпилируется. Будет работать в С. Но не в С++.

    "1234" - это string literal.

    В С это lvalue, поэтому допустима запись:
    "1234"[0] = '2';

    В С++ тип будет "массив константных char", поэтому строка выше не компилируется.
    Выражение char *m = "1234"; является известной дырой в типобезопасности, сделано для совместимости с С. strcpy(m,"one "); работает, но поведение неопределено, скорее всего будет AV.
     
  16. J0E

    J0E New Member

    Публикаций:
    0
    Регистрация:
    28 июл 2008
    Сообщения:
    621
    Адрес:
    Panama
    Интересно, что gorodon выше пишет: "String" - КОНСТАНТНАЯ переменная
    и опровергает это своим примером. В общем это тонкий вопрос и зависит от языка. В С++ принято избегать записи char *m="1234";

    Я описАлся в #8 следовало добавить char, а не заменить им первый const
    char const* const m="String";
     
  17. cppasm

    cppasm New Member

    Публикаций:
    0
    Регистрация:
    18 июл 2006
    Сообщения:
    923
    Нельзя. Читай стандарт.
    То что твой конкретный компилятор это проглотил - это везение.
    В другой раз может не повезти - получишь падение.

    От языка не зависит, зависит от конкретной реализации.

    Вот конкретный пример, собирать студией:
    Код (Text):
    1. #include<stdio.h>
    2.  
    3. int main(void)
    4. {
    5.     char *str="Hello world";
    6.     str[5]=0;
    7.     printf("%s\n",str);
    8.     return 0;
    9. }
    cl /Ox /GF test.c
    А потом запусти и наслаждайся тем что твоя программа падает :)
    MS VC++ 6.0 - падение, запись в ReadOnly секцию.
    VS 2005 - не падает, но выводит "Hello world", что немного неожиданно.
    А не падает потому что решила поумничать, и запись в константную строку вообще выкинула :)
    Вот код main():
    Код (Text):
    1. 00401000  /$  68 5CA14000   PUSH    test.0040A15C                    ;  ASCII "Hello world"
    2. 00401005  |.  68 58A14000   PUSH    test.0040A158                    ;  ASCII "%s"
    3. 0040100A  |.  E8 06000000   CALL    test.00401015                     ; printf
    4. 0040100F  |.  83C4 08       ADD     ESP,8
    5. 00401012  |.  33C0          XOR     EAX,EAX
    6. 00401014  \.  C3            RETN
    На остальных проверять лениво.

    Просто у студии это ключами регулируется, но по стандарту строковые литералы костантны и писать в них нельзя.
     
  18. J0E

    J0E New Member

    Публикаций:
    0
    Регистрация:
    28 июл 2008
    Сообщения:
    621
    Адрес:
    Panama
    Давайте читать стандарт вместе

    6.5.1 Primary expressions
    4 A string literal is a primary expression. It is an lvalue with type as detailed in 6.4.5.

    в 5м пункте детализирован тип элементов массива:

    For character string literals, the array elements have
    type char, and are initialized with the individual bytes of the multibyte character
    sequence; for wide string literals, the array elements have type wchar_t

    это протиив С++ного “array of n const char” и “array of n const wchar_t”. впрочем я плохо представляю что такое const lvalue :)

    следующий пункт говорит о проблемах:

    6 It is unspecified whether these arrays are distinct provided their elements have the
    appropriate values. If the program attempts to modify such an array, the behavior is
    undefined.

    Как видно они не из-за константности, а поскольку не уточнятся про таблицу строк :) раздельно или нет хранятся строки.

    это подтверждается в описании непортабельных расширений:

    J.5.5 Writable string literals
    1 String literals are modifiable (in which case, identical string literals should denote distinct
    objects) (6.4.5).

    и прога на ВЦ6 упала поскольку явно попросили ключем /GF котрый
    Enables the compiler to create a single copy of identical strings in the program image and in memory during execution, resulting in smaller programs, an optimization called string pooling.

    раз копия одна /GF pools strings as read-only.


    В С++ проблему запретили явно. По обычным правилам
    char *str="Hello world"; // компилироваться не должно, как
    int const i = 0; int * p = &i; // в С warning а не error
    но для cтрок сделано исключение.
     
  19. gorodon

    gorodon New Member

    Публикаций:
    0
    Регистрация:
    19 окт 2009
    Сообщения:
    301
    Код из #13 я компилил именно в VS6 без ключа /GF - компилятор разместил строки в секции .data, имеющую атрибуты чтения-запси.
    Если указать ключ /GF, то компилятор размещает строки в секции .rdata, имеющую атрибуты только чтения - естественно получим падение программы.
    Интересно, что и в том и в другом случае копия строковых литералов одна.
    Интересно, что gorodon выше пишет: "String" - КОНСТАНТНАЯ переменная
    - я думал, что компилятор должен сделать копию строки из .rdata в .data и присвоить m адрес, указывающий на строку в .data
     
  20. cppasm

    cppasm New Member

    Публикаций:
    0
    Регистрация:
    18 июл 2006
    Сообщения:
    923
    Правильно написанная программа работает правильно независимо от того, с какими ключами она скомпилирована.