Форум программистов, компьютерный форум, киберфорум
Наши страницы

C для начинающих

Войти
Регистрация
Восстановить пароль
 
 
zss
Модератор
Эксперт С++
6575 / 6137 / 2021
Регистрация: 18.12.2011
Сообщений: 15,993
Завершенные тесты: 1
#1

Распространенные ошибки - C (СИ)

02.10.2014, 12:49. Просмотров 43574. Ответов 57
Метки нет (Все метки)

Оглавление

Ошибки этапа компиляции
(В процессе компиляции выдается либо сообщение об ошибке, либо предупреждение)
- Попытка модифицировать константу через указатель
- Лишняя точка с запятой
- Отсутствие возврата значения из функции
- Приведение типа в стиле С++
- Использование = вместо ==


Ошибки этапа исполнения программы
(Во время исполнения программа прерывается с сообщением об ошибке)
- Возврат из функции локальной строки
- Выделение памяти без дальнейшего освобождения
- Использование неинциализированной переменной
- Использование функции strncpy как функции "безопасного" копирования строк
- Использование функций atoi/atof или sscanf для перевода строки в число
- Возврат ссылки/указателя на локальную переменную
- Выход за пределы массива
- Сравнение символьных массивов
- Использование чисел, записанных в других системах счисления.
- Работа с локальной копией объекта, вместо работы с самим объектом


Неправильное поведение программы на этапе исполнения
(Программа работает, но не так, как запланировано)
- Использование типов char, short и float при работе с va_arg
- Определение размера массива, переданного в качестве аргумента функции.
- "Неожиданное" закрытие окна.
- "Неожиданное" целочисленное деление в арифметических выражениях.
- Ошибки в логических выражениях.
- Использование символа цифры вместо числа
- Лишняя точка с запятой
- switch без break
- Сравнение вещественных чисел при вычислениях
- Проверки на принадлежность значения определенному интервалу.
- Неверный аргумент тригонометрических функций.
- Сравнение знаковой переменной с беззнаковой.
- Использование запятой для отделения дробной части
- Забытое выделение тела цикла for, while и операторов if else
- Локальная переменная экранирует переменную с таким же именем из вышестоящей области видимости
- Неправильное использование memset

Алгоритмические ошибки
(Неправильно составлен алгоритм программы)
- Двойная перестановка строк или элементов массива.

Ошибки ввода-вывода
- Ошибки при использовании функции scanf()!
- При работе с fgetc под Windows чтение файла обрывается при достижении буквы 'я'
- Оставление символа '\n' в потоке ввода
- scanf() - ввод текстовых строк

Ошибки, связанные с отклонением от стандарта языка
- Неверный тип функции main()
3
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Similar
Эксперт
41792 / 34177 / 6122
Регистрация: 12.04.2006
Сообщений: 57,940
02.10.2014, 12:49
Здравствуйте! Я подобрал для вас темы с ответами на вопрос Распространенные ошибки (C (СИ)):

Ошибки при компиляции - исправить ошибки в коде - C (СИ)
Помогите исправить ошибки , не понимаю в чем может быть причина ... #include <conio.h> #include <stdio.h> #include <math.h> ...

Распространенные ошибки - C++
Оглавление Ошибки этапа компиляции Программа не компилируется или компилируется с предупреждениями. Попытка модифицировать...

безопасность и распространенные ошибки - Perl
Тут наткнулся на очень интересные тексты: http://werad.narod.ru/articles/programm6.html http://werad.narod.ru/articles/programm9.html...

.NET 2.x Распространенные ошибки SEO и ASP.NET 2.0 - C# ASP.NET
Здравствуйте, существуют несколько СЕО проблем при использовании ASP.NET, ниже я опишу эти проблемы, нужно найти простое и эффективное...

Самые распространенные строки - Delphi
type Mytype = record name:string; surname:string; end; var Students:Mytype; MyFile:file of Mytype; ...

Вывести самые распространенные мужские и женские имена - C++
Имеется массив записей о студентах, каждая из которых включает поля: фамилия, имя, отчество, пол, возраст, курс. Разработать программу,...

57
zss
Модератор
Эксперт С++
6575 / 6137 / 2021
Регистрация: 18.12.2011
Сообщений: 15,993
Завершенные тесты: 1
02.10.2014, 12:55  [ТС] #2
1. Ошибки при использовании функции scanf().
Рассмотрим ошибки.
C
1
2
3
4
5
6
7
8
9
10
11
int k;
scanf("%d",&k);    //1
scanf("%d",k);     //2
scanf("%lf",&k);   //3
scanf("%d/n",&k);  //4
double a;
scanf("%lf",&a);   //5
char buf[100];
scanf("%s",buf);   //6
scanf_s("%s",buf); //7
scanf("%s",&buf);  //8
1. Здесь все правильно с точки зрения Си. Однако компилятор может выдать предупреждение (VS 2008) или даже ошибку (VS 2012):
1>d:\current\cpp\test\tset.cpp(13) : warning C4996: 'scanf': This function or variable may be unsafe. Consider using scanf_s instead.
Это нюанс исключительно микрософтовский. Предлагается использовать более защищенную функцию
scanf_s. Можете послушаться и заменить, а можете отключить это предупреждение добавив
#pragma warning(disable:4996)

или изменить уровень ошибок в свойствах проекта (VS 2008 2010):
Проект->Свойства->Свойства конфигурации->С/С++->Общие->Уровень предупреждений-> Уровень 2(W2).

2. Передано значение переменной, а не адрес. Если ранее переменной значение не присваивалось, то компилятор может выдать предупреждение о том, что используется неинициализированная переменная.

3. Вводится целое число, но в форматной строке указано иное (lf - используется double).

4. Лишние знаки форматирования в командной строке (после ввода числа будет ожидаться еще один Enter).

5. Написано правильно. Однако, тут есть нюанс (который отсутствует в iostrem).
Если локаль языка установлена русская ( setlocale(LC_ALL,"Rus") ; ), то дробная часть отделяется от целой запятой, а не точкой.

6. По формату %s водится не строка целиком, а только очередное слово до пробельного символа. Для ввода всей строки используйте gets(buf);

7. scanf_s требует передачи размера массива в дополнительном параметре:
C++
1
scanf_s("%s",buf,100);
8. Передача указателя на адрес массива тоже выполняется правильно. Но в этом случае массив должен быть статическим и объявлен в том же адресном пространстве.
2
zss
Модератор
Эксперт С++
6575 / 6137 / 2021
Регистрация: 18.12.2011
Сообщений: 15,993
Завершенные тесты: 1
02.10.2014, 12:57  [ТС] #3
2. При работе с fgetc чтение файла обрывается при достижении буквы 'я'
(Автор Evg)

C
FILE *fp;
char c;
fp = fopen ("a.txt", "r");
while ((c = fgetc(fp)) != EOF)
{
  ...
}
Переменная 'c' должна иметь тип int, а не char.

Проблема кроется не в конкретной букве 'я', а в байте со значением 255. Потому что именно это значение, рассмотренное, как char (0xff) и приведённое к типу int (0xffffffff) совпадёт со значением EOF (которое равно -1). При работе под windows с файлами в кодировке win-1251 код номер 255 имеет буква 'я'. В других кодировках этому коду может соответствовать другая буква.

Вот ссылки с пояснениями:
"я" это EOF?!
Существует ли EOF или это миф?
10 вопросов по С. part1
0
zss
Модератор
Эксперт С++
6575 / 6137 / 2021
Регистрация: 18.12.2011
Сообщений: 15,993
Завершенные тесты: 1
02.10.2014, 17:43  [ТС] #4
3. Использование типов char, short и float при работе с va_arg
(Автор Evg)
C
#include <stdio.h>
#include <stdarg.h>
 
void func (int x, ...)
{
  va_list va;
  char c1 = 0x11;
  char c2 = 0x22;
  short s1 = 0x3333;
  short s2 = 0x4444;
  float f1 = 1.25;
  float f2 = 5.75;
 
  va_start (va, x);
 
  c1 = va_arg (va, char);
  c2 = va_arg (va, char);
  s1 = va_arg (va, short);
  s2 = va_arg (va, short);
  f1 = va_arg (va, float);
  f2 = va_arg (va, float);
 
  printf ("c1=%hhx\n", c1);
  printf ("c2=%hhx\n", c2);
  printf ("s1=%hx\n", s1);
  printf ("s2=%hx\n", s2);
  printf ("f1=%f\n", f1);
  printf ("f2=%f\n", f2);
 
  va_end (va);
}
 
int main (void)
{
  char c1 = 0x11;
  char c2 = 0x22;
  short s1 = 0x3333;
  short s2 = 0x4444;
  float f1 = 1.25;
  float f2 = 5.75;
  func (0, c1, c2, s1, s2, f1, f2);
  return 0;
}
Этот код не будет работать или будет работать некорректно. Проблема кроется в том, что в языках Си/Си++ при передаче неспецифицированных аргументов начинает работать promotion для типов char, unsigned char, short, unsigned short и float. Подробнее можно почитать по ссылке:

Работа с va_arg

Правильное написание данного примера:

C
#include <stdio.h>
#include <stdarg.h>
 
void func (int x, ...)
{
  va_list va;
  char c1 = 0x11;
  char c2 = 0x22;
  short s1 = 0x3333;
  short s2 = 0x4444;
  float f1 = 1.25;
  float f2 = 5.75;
 
  va_start (va, x);
 
  c1 = (char) va_arg (va, int);
  c2 = (char) va_arg (va, int);
  s1 = (short) va_arg (va, int);
  s2 = (short) va_arg (va, int);
  f1 = (float) va_arg (va, double);
  f2 = (float) va_arg (va, double);
 
  printf ("c1=%hhx\n", c1);
  printf ("c2=%hhx\n", c2);
  printf ("s1=%hx\n", s1);
  printf ("s2=%hx\n", s2);
  printf ("f1=%f\n", f1);
  printf ("f2=%f\n", f2);
 
  va_end (va);
}
 
int main (void)
{
  char c1 = 0x11;
  char c2 = 0x22;
  short s1 = 0x3333;
  short s2 = 0x4444;
  float f1 = 1.25;
  float f2 = 5.75;
  func (0, c1, c2, s1, s2, f1, f2);
  return 0;
}
0
zss
Модератор
Эксперт С++
6575 / 6137 / 2021
Регистрация: 18.12.2011
Сообщений: 15,993
Завершенные тесты: 1
02.10.2014, 17:49  [ТС] #5
4. Определение размера массива, переданного в качестве аргумента функции.
(Автор BumerangSP):
C
1
2
3
4
5
6
7
8
9
10
11
int f(int *a) 
{
    return sizeof(a) / sizeof(*a);
}
int main() 
{
    int a[] = {1, 2, 3};
    printf("%d\n",sizeof(a) / sizeof(*a)  ); 
    printf("%d\n",f(a));
        return 0;
}
Решение: передавать размер массива как параметр функции:
C++
1
2
3
4
5
6
7
8
9
10
int f(int *a, int size) {
    return size;
}
int main()
{
    int a[] = {1, 2, 3};
    printf("%d\n",  ); 
    printf("%d\n",f(a,sizeof(a) / sizeof(*a)));
        return 0;
}
0
Evg
Эксперт CАвтор FAQ
18374 / 6421 / 441
Регистрация: 30.03.2009
Сообщений: 17,813
Записей в блоге: 28
02.10.2014, 21:33 #6
7. Возврат из функции локальной строки

Что-то похожее есть тут, но для языка Си мы имеем отдельный часто встречаемый случай при работе со строками

C
char* gen_string (void)
{
  char str[100];
  strcpy (str, "hello world");
  return str;
}
 
void func (void)
{
  p = gen_string ();
  printf ("%s\n", p);
}
В языке Си с массивом нельзя работать напрямую, как с целиковым объектом. Когда мы пишем имя переменной-массива, то семантически это означает взятие адреса на самый первый элемент массива. Т.е. "return str;" на самом деле исполнится как "return &str[0];". Т.е. из функции будет возвращён указатель на локальную переменную функции gen_string. Ну а после выхода из функции виртуально можно считать, что все её локальные переменные разрушаются

Как это дело лечить?

1. Самый честный способ - снаружи в функцию передавать буфер, куда копировать строку, и размер. По возможности при всех операциях с буфером контролировать возможный выход за границу буфера, особенно когда работаем с заранее неизвестными размерами

C
char* gen_string (char *str, int len)
{
  strncpy (str, "hello world", len);
  return str;
}
 
void func (void)
{
  char str[100], *p;
  p = gen_string (str, sizeof (str));
  printf ("%s\n", p);
}
2. Условно честный способ - внутри функции gen_string использовать static переменную. Условная честность заключается в том, что значение в строке проживёт лишь до следующего вызова функции. В большинстве случаев в реальной жизни этого достаточно, но бывает, что и нет

C
char* gen_string (void)
{
  static char str[100];
  strcpy (str, "hello world");
  return str;
}
 
void func (void)
{
  p = gen_string ();
  printf ("%s\n", p);
}
3. Честный, но плохой способ - внутри функции выделать память malloc'ом. Плохость данного метода в том, что потом кто-то должен освобождать память. А когда память выделяется на одном уровне, а освобождается на другом - такие коды часто подвержены ошибкам или утечкам памяти

C
char* gen_string (void)
{
  char *str;
  str = malloc (100);
  strcpy (str, "hello world");
  return str;
}
 
void func (void)
{
  p = gen_string ();
  printf ("%s\n", p);
  free (p);
}
1
zss
Модератор
Эксперт С++
6575 / 6137 / 2021
Регистрация: 18.12.2011
Сообщений: 15,993
Завершенные тесты: 1
03.10.2014, 20:52  [ТС] #7
5. "Неожиданное" закрытие окна под Windows.
Когда консольное приложение запускается непосредственно из среды программирования,
то после выполнения последнего оператора программы ( return 0; ) окно закрывается.
Вставляйте оператор, ожидающий ввода символа с клавиатуры перед return:
C
1
2
3
4
#include <stdlib.h>
.....
system("pause");
return 0;
или
C
1
2
3
4
5
#include <stdio.h>
.....
puts("Нажмите Enter для завершения");
getchar();
return 0;
или с использованием библиотеки низкоуровневого ввода conio.h
(на *NIX системах она не используется):
C
1
2
3
4
#include <conio.h>
.....
getch();
return 0;
0
zss
Модератор
Эксперт С++
6575 / 6137 / 2021
Регистрация: 18.12.2011
Сообщений: 15,993
Завершенные тесты: 1
04.10.2014, 10:24  [ТС] #8
6. Оставление символа '\n' в потоке ввода:
C
1
2
3
4
5
int n;char c;
puts("введите n:");
scanf("%d",&n; // вводится число после нажатия Enter
puts("введите символ:");
scanf("%c",&c); // хотим ввести символ на новой строке, но вместо него вводится '\n'
исправление ошибки
C
1
2
3
4
puts("введите n:");
scanf("%d\n",&n;  // вводим число и пропускаем конец строки
puts("введите символ:");
scanf("%c",&c); // вводится символ на новой строке ( !!!  '\n' второй строки еще не прочитали).
или
C
1
2
3
4
5
puts("введите n:");
scanf("%d",&n;  // вводим число 
getchar(); // пропускаем конец строки
puts("введите символ:");
scanf("%c",&c);
0
zss
Модератор
Эксперт С++
6575 / 6137 / 2021
Регистрация: 18.12.2011
Сообщений: 15,993
Завершенные тесты: 1
04.10.2014, 10:30  [ТС] #9
8. Выделение памяти без дальнейшего освобождения
При выделении памяти обязательно освобождайте её перед выходом из функции,
во избежание утечки памяти:
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <stdio.h>
#include <memory.h>
#include <locale.h>
int f(int n)
{
    int i,sum;
    int *a;
    a=(int*)malloc(n*sizeof(int)); // выделена память
    sum=0;
    for(i=0;i<n;i++)
    {
        a[i]=i*i;
        sum+=a[i];
    }
    free(a); // освобождена память
    return sum;
}
int main()
{
    int s;
    s=f(5);     
    printf("s=%d\n",s);
    s=f(10);
    printf("s=%d\n",s);
    setlocale(LC_ALL,"Rus");
    printf("\nНажмите Enter для завершения");
    getchar();
    return 0;
}
0
zss
Модератор
Эксперт С++
6575 / 6137 / 2021
Регистрация: 18.12.2011
Сообщений: 15,993
Завершенные тесты: 1
04.10.2014, 10:57  [ТС] #10
9. Использование неинциализированной переменной

C
1
2
int x; // предполагалось, например, int x=1;
printf("%d",x); // выведет мусор (то, что находилось в переменной x)
Часто встречается вариант с использованием неинициализированного счётчика или переменной для подсчёта суммы:
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
#include <locale.h>
#define arrSize  3
int main()
{
    int i,arr[arrSize] = {1, 2, 3};
    int sum;                // надо писать int sum=0;
    for (i=0; i < arrSize; i++)
        sum += arr[i]; // мы к "мусору" добавляем значение элемента массива
    printf("%d\n",sum); // выведет не то, что вы ожидали
    setlocale(LC_ALL,"Rus");
    printf("\nНажмите Enter для завершения");
    getchar();
    return 0;
}
Такой код может привести к ошибке на этапе исполнения:
Run-Time Check Failure #3 - The variable 'sum' is being used without being initialized.
Также компилятор может выдать предупреждение об ошибке:
1>d:\current\cpp\test\test.c(9) : warning C4700: использована неинициализированная локальная переменная "sum"
Для этого надо установить соответствующий уровень предупреждений компилятора.
В Visual Studio это делается через команду меню
Проект->Свойства->Свойства конфигурации->С/С++->Общие->
Уровень предупреждений->Уровень 4 (/W4).
0
Evg
Эксперт CАвтор FAQ
18374 / 6421 / 441
Регистрация: 30.03.2009
Сообщений: 17,813
Записей в блоге: 28
12.10.2014, 14:06 #11
10. "Неожиданное" целочисленное деление в арифметических выражениях.
Следует помнить, что результат деления целого на целое - тоже целое:
C
1
2
3
4
5
int n;double y;
n=2;
y = 1/n;// y будет равен нулю
y = pow(y, 1/3 ); // возведение в нулевую степень
z = y + n/(n+1); // прибавляется ноль
Рекомендации:
Все константы, которые явно не должны быть целыми, делайте действительными
(записывайте с точкой, первое время можете даже после точки ставить нуль 1.0 ):
C
1
2
3
4
5
int n;double y;
n=2;
y = 1./n;// y будет равен 0.5
y=pow(y, 1./3. ); //извлечение корня кубического
z=y + n/(n+ 1. ); // прибавляется 0.666666666666
Там, где в выражении нет констант, используйте оператор приведения
C
1
2
3
4
int n,m;
n=1;
m=2;
double y = (double)n/m;

Пояснения, почему так происходит.

Во всех двухаргументных арифметических операциях (кроме операций сдвига) оба аргумента должны иметь один и тот же тип. Если аргументы имеют разный тип, то компилятор строит неявное приведение типа для одного их аргументов, чтобы в конечном итоге выполнить арифметическую операцию над значениями одного и того же типа. Чтобы выяснить, который из двух аргументов нужно приводить к типу другого, в стандарте языка существуют правила, которые в отдельных тонких моментах даже различаются для Си и Си++. В общем виде они довольно сложные для восприятия начинающими, а потому сделаю лишь краткое пояснение.

В случае различных типов один из типов является более "широким", чем другое. А потому в двухаргументной операции всегда аргумент более "узкого" типа приводится к более "широкому" типу. Проверка идёт примерно в таком порядке:
  • Вещественный тип всегда считается более широким, чем целочисленный
  • Когда оба типа вещественные или оба целочисленные, то тип с большим размером всегда считается более широким, чем тип с меньшим размером
  • В случае равенства размеров для целочисленных типов беззнаковый тип считается более широким, чем знаковый

В итоге код

C
float f;
int i;
unsigned u;
long long ll;
x + f;
u + f;
i + u;
ll + u;
эквивалентен

C
int i;
unsigned u;
float f;
((float)x) + f; /* тип результата - float */
((float)u) + f; /* тип результата - float */
((unsigned)i) + u; /* тип результата - unsigned */
ll + ((long long)u); /* тип результата - long long */
Если мы вернёмся к делению, то следующий код

C
int a, b;
float f;
f = a / b;
эквивалентен

C
int a, b, tmp;
float f;
/* сначала выполняем целочисленное деление
 * (при котором отбрасывается остаток) */
tmp = a / b;
/* и только потом полученный результат (с утерянной
 * дробной частью) преобразуется в вещественный тип */
f = (float) tmp;
А потому, чтобы выполнить нормальное вещественное деление нужно хотя бы один из аргументов превратить в вещественный тип, после чего второй аргумент превратится в вещественный тип автоматически по выше описанным правилам. Т.е. явно написать

C
int a, b;
float f;
f = ((float)a) / b;
или

C
int a, b;
float f;
f = a / ((float)b);
что будет эквивалентно

C
int a, b;
float f;
f = ((float)a) / ((float)b);
Если один из аргументов является константой, то можно написать её в виде "3.0" (с вещественной дробной частью), что автоматически приведёт её тип к вещественному
6
TheCalligrapher
С чаем беда...
Эксперт CЭксперт С++
3969 / 2193 / 553
Регистрация: 18.10.2014
Сообщений: 3,802
24.10.2014, 11:30 #12
11.Использование функции strncpy как функции "безопасного" копирования строк

Функция strncpy появилась в стандартной библиотеке языка С для решения одной узкоспециализированной задачи - перевода классических нуль-терминированных строк в так называемые строки фиксированной ширины. Строки фиксированной ширины - это особый способ представления строк, который использовался в файловой системе Unix во времена, когда язык С был еще очень молодым.

В формате фиксированной ширины строка хранится в соответствии со следующими правилами:
1. Все строки хранятся в буфере заранее заданной фиксированной ширины N
2. Если строка имеет длину меньше N, то все лишние позиции в строке заполняются нулевыми символами
3. Если строка имеет длину ровно N, то она полностью заполняет буфер и не заканчивается нулевым символом

В частности, файловая система Unix System V использовала такие строки длины 14 для представления имен файлов. Такой формат представления строк был выбран с одной-единственной целью - сэкономить один байт при представлении имен максимальной длины. При использовании классических нуль-терминированных строк имя длины 14 потребовало бы 15 байт, в то время как в соответствии с соглашениями формата фиксированной ширины такая строка умещается в 14 байт.

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

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

C
1
2
3
4
char target1[14], target2[14];
 
strncpy(target1, "hello", sizeof target1);
strncpy(target2, "hello, world !", sizeof target2);
Обратите внимание, что во втором случае целевой буфер не будет нуль-терминирован, в полном соответствии со спецификацией строк фиксированной ширины. А в первом случае он будет, неформально выражаясь, нуль-терминирован 9 раз.

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

Разумеется, пользователи функции быстро заметили, что она не выполняет нуль-терминацию целевой строки, когда при копировании был достигнут предел длины целевого буфера. В результате родилась следующая неприглядная идиома

C
1
2
3
4
char target[BUFFER_SIZE];
...
strncpy(target, source, BUFFER_SIZE);
target[BUFFER_SIZE - 1] = '\0';
Т.е. после использования strncpy надо обязательно не забыть выполнить нуль-терминацию целевой строки "на всякий случай". Именно в рамках такого применения функция strncpy обрела вторую жизнь и даже умудрилась войти в некоторые учебники как пример "безопасной" работы со строками.

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

Другими словами, если функию strncpy еще можно как-то заставить работать для безопасного копирования строк - при помощи ручной нуль-терминации целевой строки (как в примере выше), то использование strncpy без такой нуль-терминации всегда является ошибкой. Однако в реальном коде часто можно встретить именно такое использование strncpy с полным игнорированием необходимости обеспечить нуль-терминацию.

К сожалению, стандартная библиотека языка С не предоставляет хорошей функции, выполняющей именно ограниченное по длине копирование обычных С-строк (это еще одна причина популярности strncpy в этой роли). Поэтому реализации стандартной библиотеки в системах Unix традиционно предоставляют свою де-факто стандартную функцию - strlcpy - предназначенную именно для этой цели. strlcpy - это именно функция безопасного (т.е. ограниченного по длине) копирования обыкновенный нуль-терминированных строк. Если вы работаете на платформе, не предоставляющей вам функции strlcpy, то лучше написать эту функцию самостоятельно, чем замусоривать ваш код некачественным применением strncpy.

Но, если уж вам все таки хочется пользоваться strncpy, то помните - эта функция не гарантирует того, что ваша строка будет правильно нуль-терминирована. Не забываете обеспечить корректную нуль-терминацию самостоятельно.
9
Evg
Эксперт CАвтор FAQ
18374 / 6421 / 441
Регистрация: 30.03.2009
Сообщений: 17,813
Записей в блоге: 28
24.10.2014, 16:38 #13
Цитата Сообщение от TheCalligrapher Посмотреть сообщение
По какой-то необъяснимой причине эти стандартные функции малоизвестны среди начинающих программистов
Причина вполне стандартная, как и для многих других ошибок - неправильно рассказывали в школе/институте из-за того, что преподаватель сам плохо разбирается в вопросе

Добавлено через 1 час 33 минуты
Цитата Сообщение от TheCalligrapher Посмотреть сообщение
Однако, к сожалению, как и atoi/atof, функция sscanf приводит к неопределенному поведению в случае арифметического переполнения
Тут есть ещё дополнительная засада. Выдержка из linux'ового man'а:

ERRORS
...
ERANGE The result of an integer conversion would exceed the size that can be stored in the corresponding integer type.
и действительно, на практике это имеет место быть:

C
#include <stdio.h>
#include <errno.h>
 
int main (void)
{
  int x;
  sscanf ("99999999999999999999999", "%d", &x);
  printf ("x=%d, errno=%d\n", x, errno);
  return 0;
}
Код
$ gcc t.c
$ ./a.out
x=2147483647, errno=34
Однако под solaris'ом ничего такого в man'е не указано, и поведение иное:

Код
$ gcc t.c
$ ./a.out
x=-159383553, errno=0
Т.е. имеет место быть дополнительный источник проблем, когда человек, прочитав документацию и запуская на одной платформе, видя, что всё работает, делает предположение, что на других платформах тоже будет работать
0
TheCalligrapher
С чаем беда...
Эксперт CЭксперт С++
3969 / 2193 / 553
Регистрация: 18.10.2014
Сообщений: 3,802
24.10.2014, 18:25 #14
Цитата Сообщение от Evg Посмотреть сообщение
и действительно, на практике это имеет место быть:
Да, конечно, если заглянуть в реализацию стандартной библиотеки, то обнаружится, что 'atoi' "почти всегда" просто напросто вызывает 'strtol'. Таким образом хорошее поведение 'strtol' тут же наследуется и 'atoi'. Я не удивлюсь, если увижу аналогично реализованную 'sscanf'.

Но спецификация языка продолжает настаивать на том, что 'atoi' ведет себя (или может вести себя) плохо:

7.20.1 Numeric conversion functions
1 The functions atof, atoi, atol, and atoll need not affect the value of the integer expression errno on an error. If the value of the result cannot be represented, the behavior is undefined.
Более того, даже спецификация POSIX в этом отношении тоже согласна со стандартом и открыто говорит, что 'atoi' ведет себя как 'strtol', но не обнаруживает ошибок.

Аналогичное утверждение сделано о функциях группы 'scanf'.

Если заглянуть в такой документ, как Rationale for International Standard-Programming Languages-C, то там открыто пишут, что уже в времена первого стандарта языка С (C89/90) опасность функция группы 'ato...' была всем понятна. Именно по этой причине, в частности, первое дополнение к стандарту, вышедшее в в 1995 году и добавившее, кроме прочего, в стандартную библиотеку функции работы с wide character строками, не ввела "широких" версий для функций этой группы. Там же сказано, что функции группы 'ato...' считаются "поглощенными" (subsumed) функциями группы 'strto...', но не были исключены из языка полностью по причине их широкого использования в старом коде (см. 7.20.1.1/25).

Другими словами, мне не совсем понятно, почему комитет по стандартизации не хочет "спасти" функции группы 'ato...'. Ведь на первый взгляд не составит никакого труда просто уточнить их спецификацию и сделать их безопасными аналогами функций группы 'strto...'. Но этого не происходит. Да, с одной стороны, как сказано в rationale, эти функции считаются "ненужными". Но с другой, группа продолжает поддерживаться: 'atoll' была добавлена относительно "недавно".
0
Evg
Эксперт CАвтор FAQ
18374 / 6421 / 441
Регистрация: 30.03.2009
Сообщений: 17,813
Записей в блоге: 28
24.10.2014, 18:50 #15
Цитата Сообщение от TheCalligrapher Посмотреть сообщение
Но спецификация языка продолжает настаивать на том, что 'atoi' ведет себя (или может вести себя) плохо
Корень подобных проблем ты уже озвучивал выше - стандарты никто не читает, все пытаются делать выводы на основании того, что на их собственной системе программирования всё работает. Я тут просто наглядно продемонстрировал к чему может привести такой неправильный подход. Для начинающих само понятие другой платформы уже тяжело к восприятию, так что может быть тут уже будет перебором засовывать всё это в ошибки для начинающих, т.к. это уже по сути следующий уровень
0
24.10.2014, 18:50
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
24.10.2014, 18:50
Привет! Вот еще темы с ответами:

Вывести самые распространенные женские и мужские имена - C++
Помогите решить задачу пожалуйста! Написать программу, которая формирует файл записей данной структуры Type Student=Record ...

Вирус блокирует выход на сайт вк и другие распространенные сайты - Удаление вирусов
я чайник поэтому если что то не загрузилось..напишите пожалуйста, и помогите... второй день вожусь с этим. Hosts чистый, drweb выявил один...

Найти и вывести самые распространенные женские те мужские имена - Pascal ABC
О каждом студенте факультета доступна следующая информация: фамилия, имя, отчество, пол, возраст, курс. Найти и вывести самые...

Какие самые распространенные бесплатные спам-фильтры Вам известны? - Безопасность сайтов
Всем привет понимаю что не заслуживаю как новенький о помощи но все же попрошу помогите . Вопрос жизни . Какие самые распространенные...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
15
Ответ Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2017, vBulletin Solutions, Inc.